How to Mock Dependencies in PHPUnit Using Mockery

How to Mock Dependencies in PHPUnit Using Mockery

Learn how to mock dependencies in PHPUnit using Mockery. Improve unit test efficiency by isolating components and testing behavior independently.

Introduction

Unit tests should focus on testing a single unit of code without being affected by external dependencies such as databases, APIs, or third-party services. Mocking allows us to replace real dependencies with controlled, test-friendly versions to ensure reliable and isolated testing.

Mockery is a powerful mocking framework for PHP that helps create flexible and expressive test doubles for unit testing.

With Mockery, you can:

  • Simulate object behavior without relying on real implementations
  • Control return values and method calls dynamically
  • Verify interactions with dependencies (method calls, arguments, etc.)
  • Write isolated unit tests that run faster and are more reliable

This guide covers:

  • Installing and setting up Mockery with PHPUnit
  • Creating and using mocks for unit testing
  • Controlling method return values and expectations
  • Verifying mock interactions in test assertions

1. Installing Mockery for PHPUnit

Mockery is installed via Composer as a development dependency.

Run the following command in your PHP project:

composer require --dev mockery/mockery

Once installed, you can use Mockery in your PHPUnit tests without additional setup.

To ensure Mockery is available, check your composer.json:

"require-dev": {
    "phpunit/phpunit": "^10.0",
    "mockery/mockery": "^1.5"
}

2. Creating and Using a Mock Object in PHPUnit

Consider a UserService class that depends on a UserRepository for database operations.

UserRepository.php (Dependency to Mock)

<?php

namespace App;

class UserRepository
{
    public function findUserByEmail($email)
    {
        // Normally, this would query the database
    }
}

UserService.php (Class to Test)

<?php

namespace App;

class UserService
{
    protected $userRepository;

    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function getUserEmail($email)
    {
        $user = $this->userRepository->findUserByEmail($email);
        return $user ? $user['email'] : null;
    }
}

Testing UserService with a Mocked UserRepository

Instead of testing with an actual database, we mock the UserRepository dependency using Mockery.

<?php

use Mockery;
use PHPUnit\Framework\TestCase;
use App\UserService;
use App\UserRepository;

class UserServiceTest extends TestCase
{
    public function testGetUserEmailReturnsCorrectEmail()
    {
        // Create a mock for UserRepository
        $mockRepo = Mockery::mock(UserRepository::class);
        
        // Define behavior: When findUserByEmail() is called, return a mock user
        $mockRepo->shouldReceive('findUserByEmail')
                 ->with('john@example.com')
                 ->once()
                 ->andReturn(['email' => 'john@example.com']);
        
        // Inject mock into the service
        $userService = new UserService($mockRepo);
        
        // Run test and assert expected behavior
        $this->assertEquals('john@example.com', $userService->getUserEmail('john@example.com'));
    }

    public function tearDown(): void
    {
        Mockery::close();
    }
}

What This Test Does:

  1. Creates a mock of UserRepository.
  2. Defines behavior – When findUserByEmail() is called with 'john@example.com', return a fake user.
  3. Injects the mock into UserService.
  4. Runs the test to confirm the correct email is returned.

Mockery ensures the method is called exactly once, helping detect unexpected behavior.

3. Controlling Mocked Method Behavior

Returning Different Values Based on Input

Mockery allows setting different return values for different method arguments.

$mockRepo->shouldReceive('findUserByEmail')
         ->with('alice@example.com')
         ->andReturn(['email' => 'alice@example.com']);

$mockRepo->shouldReceive('findUserByEmail')
         ->with('unknown@example.com')
         ->andReturn(null);

Returning Default Values for Any Input

$mockRepo->shouldReceive('findUserByEmail')
         ->andReturnUsing(fn($email) => ['email' => $email]);

This returns a fake user object with any email passed as an argument.

4. Verifying Mock Interactions

Mockery can check whether specific methods were called, how many times, and with what arguments.

Checking Method Call Count

$mockRepo->shouldReceive('findUserByEmail')
         ->once();  // Ensures method is called exactly once

Ensuring a Method is Never Called

$mockRepo->shouldReceive('deleteUser')
         ->never();  // Test fails if deleteUser() is ever called

Validating Method Arguments

$mockRepo->shouldReceive('findUserByEmail')
         ->with(Mockery::on(fn($arg) => filter_var($arg, FILTER_VALIDATE_EMAIL)))
         ->andReturn(['email' => 'valid@example.com']);

This ensures that only valid emails are passed to findUserByEmail().

5. Spying on Real Objects Instead of Fully Mocking

Mockery can partially mock real objects, allowing real method execution while tracking interactions.

$realRepo = new UserRepository();

$spyRepo = Mockery::spy($realRepo);

$spyRepo->findUserByEmail('john@example.com');

$spyRepo->shouldHaveReceived('findUserByEmail')
        ->with('john@example.com')
        ->once();

Why Use Spies?

  • Ensures the method is actually executed while tracking interactions.
  • Useful for testing loggers, event dispatchers, or state changes.

6. Cleaning Up Mockery After Each Test

To avoid mocking issues across tests, always close Mockery after each test.

public function tearDown(): void
{
    Mockery::close();
}

Alternatively, use PHPUnit’s afterEach() hook:

protected function tearDown(): void
{
    Mockery::close();
}

This ensures that each test starts with a fresh mock environment.

7. Best Practices for Using Mockery in PHPUnit

  • Only mock external dependencies – Don't mock the class being tested.
  • Use shouldReceive() carefully – Avoid setting unnecessary expectations.
  • Close Mockery after tests – Prevent leaks across test cases.
  • Use spies for partial mocking – When real execution needs tracking.
  • Verify correct method calls – Ensure tests validate the expected behavior.

Conclusion

Mocking dependencies in PHPUnit using Mockery allows for fast, reliable, and isolated unit tests.

This guide covered:

  • Installing Mockery for PHPUnit
  • Creating and using mock objects
  • Controlling mocked method behavior
  • Verifying interactions and method calls
  • Using spies for partial object mocking

By applying these techniques, PHP unit tests become more efficient and maintainable.

Leave a Reply