Skip to content

Unit Test: Test Doubles

Fabian Schmengler edited this page Oct 10, 2017 · 2 revisions

These exercises give an introduction to unit tests in Magento and test doubles.

Exercise 1: Fake

This exercise is based on the contacts module, as developed in the Integration-Test: Controllers exercise. If you did not complete it, clone https://github.com/tddwizard/magento2-exercise-contact.git into app/code/TddWizard/ExerciseContact and check out the branch solution-integration-test-5.

The goal of this exercise is to refactor the existing solution to a thin controller and move the business logic in a new domain layer. We will create a service from scratch with TDD, then replace the code in the controller with delegation to the service. For anything non-trivial this should be how you start out in the first place.

Create a new class: Service\CreateInquiry with a method createFromInput(string message, string email = null)

For the first exercise, only implement that it saves an inquiry with message and email if the input is valid, and throw an exception if it is not valid.

The class will only need two dependencies: InquiryRepositoryInterface and InquiryInterfaceFactory. Their real implementations cannot be used - we do not have a database in unit tests!

So, for tests, create a fake implementation of the repository that saves the inquiries in memory. For this test, only the save() method actually needs to be implemented, the others can stay empty.

Example:

class InquiryMemoryRepository implements InquiryRepositoryInterface
{
    public $inquiries = [];
    public function save(InquiryInterface $inquiry)
    {
        $this->inquiries[] = $inquiry;
    }
    
    //...
}

By making $inquiries public, you can easily inspect the saved inquiries from the test.

The (generated) InquiryInterfaceFactory does not have an interface so that we cannot write our own implementation. We need a stub, that PHPUnit can generate for us with $this->createMock(InqiuryInterfaceFactory::class), if the class has been generated. The Magento unit testing framework takes care of generation. For the Inquiry model that we let the factory return, we can use $this->createPartialMock(Inquiry::class), this way the getter and setter methods still work and just the dependencies are stubbed.

Check out solution-unit-test-1 to see a solution.

Exercise 2: Stub

Now the email address should come from the customer if they are logged in.

The service needs a new dependency: Customer\Session. This class does not have an interface, so it's not so easy to replace. Use the PHPUnit mock feature to create a stub (mock without expectations).

Check out solution-unit-test-2 to see a solution.

Exercise 3: Mock

Finally, let the service also take care of success and error messages. It should not throw exceptions for invalid input anymore, but deliver error messages to the user.

The service needs a new dependency again: \Magento\Framework\Message\ManagerInterface. A fake implementation similar to exercise 1 would be possible, but try a different approach: a PHPUnit mock. Write expectations for addErrorMessage and addSuccessMessage.

Check out solution-unit-test-3 to see a solution.

Integration

Finally, replace the previous code in the Save controller. The integration tests should tell you if it was successful!

Check out solution-unit-test-integration to see a solution.