jrdavidson's avatar

Setting up types of method parameters for mocks

What I'm trying to unit test here is my create method on my service class. I'm setting up the expectations of what dependencies should be doing. I want to make sure that the types are correct for the repository parameters. What I'm trying to figure out is how to handle the situation where the array passed into create has a key of activated_at. I don't need it to contain a specific value but just contain the key with a string datetime value.

/**
 * @test
 */
public function it_can_create_a_user_with_an_activation()
{
    $data = [];
    $userMock = $this->mock(User::class);
    $repositoryMock = $this->mock(UserRepository::class);
    $service = new UserService($repositoryMock);

    $repositoryMock->expects()->create(\Mockery::type('array'))->once()->andReturns($userMock);
    $repositoryMock->expects()->activate($userMock, \Mockery::type('string'))->once()->andReturns($userMock);

    $service->create(array_merge(['activated_at' => now()->toDateTimeString()], $data));
}
0 likes
5 replies
bugsysha's avatar

You either have a validation inside of that create method that will assert that you have the required key, or use PHP8 attributes, or even better, use interfaces and avoid arrays.

martinbean's avatar

@jrdavidson I think you really need to step back from your tests and actually determine what it is you‘re testing. This is the second question now and it just seems you’re mocking for the sake of mocking.

You should be able to look at a test case and quickly grok what it’s actually testing. I’m looking at the above test case and all I see is mocks, mocks, expectations, and expectations. I can’t immediately see what class or method or return values you’re actually trying to test.

jrdavidson's avatar

@martinbean I'm just trying to write some unit tests and I've seen from other replies you've made to other posts that unit tests should not hit the database. So I thought that meant not creating records in the database.

martinbean's avatar
Level 80

@jrdavidson You’re right in that a unit test shouldn’t be interacting with things like the database, otherwise you’re not longer testing a unit of your application; you’re testing how that unit interacts with other things, in which case it’s now an integration test. But this doesn’t mean you should write what a feature or integration tests, but then mock everything in oblivion so you can call it a “unit” test.

So, getting back to the problem at hand, I don’t see what it is you’re actually trying to test. If you’re service is instantiated with a repository instance, then that’s what you should be mocking. You then don’t need to mock everything the original repository used because it’s not going to use it any more; the repository is a mock. It has none of its original code contents any more. So instantiate your service with the mock, call your method, and assert the service called the appropriate method was called on your repository mock:

$repository = $this->mock(UserRepository::class, function ($mock) {
    $mock
        ->shouldReceive('someMethod')
        ->once()
        ->with($someArg)
        ->andReturn($someResult);
});

$service = new UserService($repository);

$service->create([
    'activated_at' => Carbon::now(),
]);

But I don‘t really know why you’re trying to unit test this. If this is creating a user and you want to check that user has some state, then it makes more sense to make this an integration test than try and unit test it.

Call your methods and assert a row was written to the database with the values you’re expecting.

1 like
jrdavidson's avatar

@martinbean Thank you for that further explanation and feedback on my current test. Will do in the future.

Please or to participate in this conversation.