# Why mocking isn't as scary as you might think

PHP - Testing - July 14th, 2019
Why mocking isn't as scary as you might think

Mocking is a part of testing jargon which might sound intimidating and complicated at first but is, in reality, an easy concept to wrap our head around.

In a Nutshell, mocking, like the name might suggest, is used to mock real objects in your application, usually for unit testing.

# So why do we need to mock our objects?

Imagine the following scenario: We have a user class which we want to unit test. We want to assert that we can register a new user and persist him in the database. However, as a side effect, our application also sends a welcome email to the newly registered user. But we don't care about an email being sent in our unit tests. We only care that the user is saved to the database so our tests are kept simple and fast.

This sounds like we want to mock the actual behavior of the mailer class and only assert that the right data is sent to the mailer and nothing else.

So here's how our PHPUnit test case might look like:

public function test_it_saves_a_user_to_the_database()
{
    $mock = Mockery::mock(Mailer::class);

    $mock->shouldReceive('to')->with('newUser@example.com')->once()->andReturnSelf()
         ->shouldReceive('send')->once()->andReturn(true);

    $user = User::create([
        'email' => 'newUser@example.com',
        'name' => 'John Doe'
    ]);

    $this->assertDatabaseHas('users', [
        'email' => 'newUser@example.com',
        'name' => 'John Doe'
    ]);
}

In this case, we use the popular mocking framework Mockery. As you can see, it is actually very readable what the framework does in the background. First, we tell Mockery to mock a certain object in our application, then we tell the mocked object which methods we expect to be called, what parameters should be passed to the method and finally, what should be the returned value.

Mockery supports PHPUnit and PHPSpec and it automatically runs several assertions in our test case.

# Mocking a third party service.

Your application probably interacts with some sort of third-party service, where you send some HTTP requests to. When running tests this poses two problems:

  • Sending an HTTP request during testing severely decreases the performance of your tests.
  • It is impossible to test exception handling if the integrated service throws an unexpected error.

With mocking, we can solve both of these problems. Imagine a service where we can pass a list of ingredients and receive the name of a possible recipe. In our test, we want to assert that the returned recipe is stored in the database.

public function test_it_saves_a_recipe_name_to_the_database()
{
    $mock = Mockery::mock(RecipeService::class);

    $mock->shouldReceive('ingredients')->with(['raspberries'])->once()->andReturnSelf()
         ->shouldReceive('getRecipe')->once()->andReturn([
             'name' => 'Raspberry Pie'
         ]);

    $recipe = Recipe::storeFromIngredients(['raspberries']);

    $this->assertDatabaseHas('recipes', [
        'name' => 'Raspberry Pie'
    ]);
}

We now have successfully tested, that the returned recipe name is stored in our database.

But what if the service returns nothing because of a server error? Without mocking this would be impossible to test. But since we can mock the RecipeService class, we can write a new test case.

public function test_it_saves_a_user_to_the_database()
{
    $this->expectException(ServiceNotAvailableException::class);

    $mock = Mockery::mock(RecipeService::class);

    $mock->shouldReceive('ingredients')->with(['raspberries'])->once()->andReturnSelf()
         ->shouldReceive('getRecipe')->once()->andReturn([
            'error' => '500',
            'message' => 'Something went wrong.'
         ]);

    $recipe = Recipe::storeFromIngredients(['raspberries']);

    $this->assertDatabaseMissing('recipes', [
        'name' => 'Raspberry Pie'
    ]);
}

Notice that we set the excpeted exception in the first line of the test case.

# In conclusion

Mocking allows us to easily mock objects and eliminate side effects of our application which are not important for our unit tests.
It removes the dependency of third-party services in testing and keeps our test suite performant.

Mocking always sounded like a very high-level concept to me but once I took the time and started to look into it, I realized that it is actually a simple concept and I hope you now feel like that as well.