logo
podcast Podcast
get help Get Unstuck

Don't forget this when writing mock assertions

Or it might pass when it shouldn't

Joel Clermont
Joel Clermont
2024-11-05

I've seen tests where almost every single class the code interacts with is mocked out. For me, I try not to overuse mocking in my tests, but I do find mocking useful when you want to limit the scope of what you're testing.

Mocking benefits us not only to avoid calls into unwanted classes, or even external API calls, but also by gaining confidence that the class is called in the exact way you expect.

For example, on a recent project, we were using a third-party feature flag service. I didn't want it called in my tests, so I mocked it out.

But I also wanted to assert that a certain method on this service was being called with the expected feature flag enum as a parameter:

$featureMock = $this->mock(ThirdPartyService::class);
$featureMock->shouldReceive('active')
    ->with(FeatureFlag::SOME_FLAG_NAME)
    ->andReturnTrue();

// rest of test execution here

That looks good, and my test passed, but something very important is missing in the mock assertion. Did you notice it? If not, go back and look again before reading on.

I never specified how many times the method should be called. Without this part of the assertion, my test will pass even if my mocked service was never called at all.

That certainly isn't what I intended.

Instead, it should look like this:

$featureMock = $this->mock(ThirdPartyService::class);
$featureMock->shouldReceive('active')
    ->with(FeatureFlag::SOME_FLAG_NAME)
    ->once()
    ->andReturnTrue();

Now, my assertion is much stronger since it will fail if the feature flag service is not called at all.

There are other ways to assert the number of times a method is called, such as twice(), times(3), etc. Your circumstances will dictate how many times a mock should be called, but the key point to remember is that you should always include that number in your mock assertions.

Here to help,

Joel

P.S. Want to ask me any sort of Laravel question you can imagine? I love answering, but even better, you can ask that question to a whole group of Laravel developers in our community.

Toss a coin in the jar if you found this helpful.
Want a tip like this in your inbox every weekday? Sign up below 👇🏼
email
No spam. Only real-world advice you can use.