fetzi.dev

Testing with phpspec

6 minutes

Testing a web application can be very challenging. Should tests cover the whole system (frontend, businesslogic, backend, …) or do you want to test these parts on their own? In this article, I’m explaining why I am using phpspec for testing my business logic at the unit level.

I’ve used xUnit for testing over years now, but I often struggled with some recurring code that exists in almost every xUnit test class in the past. There is always this SUT class instance variable lying around, dependencies get injected into a constructor or getting set in some sort of setup method, these dependencies need to be also stored in class properties.

But then a colleague at work introduced phpspec and after a short learning phase I dropped xUnit and fell in ❤️️ with phpspec.

What the hell is phpspec?

phpspec is a toolset for behavior driven development. It enforces you to write your test before the actual implementation. I know that this statement is applicable to every testing tool but the main difference to any xUnit implementation is that you will have serious troubles writing tests against filthy architecture that already exists.

Key features/advantages for me

Tests first

When you start with tests first, phpspec will generate the class/method skeleton(s) for you based on the method usage inside the tests. This is one of the main advantages of phpspec for me. First write tests that describe the behavior of a method, define the behavior of external dependencies and mabybe assert against the return value of the method under test. Afterward you implement the code to make all tests pass.

If you encounter an additional edge case you can cover it with an additional test case. At first it was very hard for me to get used to this methodology but it enforces you to completely specify the behavior of the method right before the implementation. If you start with a sloppy test set you will have to introduce breaking changes to the implementation and this forces you to also update all test cases related to this method.

Initialization and mocking

Each specification file has a let function that is treated like the constructor.

<?php

function let(DependencyClass $dependencyClass)
{
    $this->beConstructedWith($dependencyClass);
}

This instructs phpspec that the class under test needs to be constructed with an instance of DependencyClass. Therefore prophecy, a mocking framework is included in phpspec. The test runner ensures that for each test a mock instance of the required class gets passed to the class constructor.

A nifty side effect of the parameter definition in the let function: When you need a mock object in a test case you simply define the dependency as a method parameter and you will get the same instance as already given in the let method. This is very useful if you have expectations on the mocks that can be reused in some test cases.

<?php

function it_should_do_some_work(DependencyClass $dependencyClass) {}

To actually mock a method you simply do the method call and append the expectation about the return value.

<?php

$dependencyClass->method()->willReturn('a string');;

This defines the return value of the method call, but often we want to be certain that a method gets called. This can be accomplished by appending shouldBeCalled() method.

<?php

$dependencyClass->method()->willReturn('a string')->shouldBeCalled();

Don’t mock parts of the SUT

In phpspec it is not possible to mock abstract methods inside a test class and this is also an absolute no-go in test driven development. You should always be able to test the real implementation of a class without the need of mocking a method on the SUT.

Stop asserting the SUT’s state

In xUnit tests you often check the SUT’s current state by asserting the value of a class property. This check is not possible in phpspec. Basically you can specify the behavior of external dependencies und assert against the return value of the method call.

new Class() -> legacy code

Let’s take a look at a short example, that may exist in some form in your legacy code:

<?php

class DemoService {

  private $repository;

  public function __construct()
  {
    $this->repository = new Repository();
  }

  // ...
}

Testing any method inside this class is impossible with phpspec (caused by the new Repository()call) but it is possible with Reflection in PHPUnit. This limitation in phpspec prevents me/you from writing such creepy code.

How to write phpspec tests

Each test case sits in its own function that has a meaningful name about the tests behavior. The name starts with the prefix “it” and seperates words with an underscore. This results in a function name like it_should_do_some_work. This naming convention is great for converting the function’s name into a human readable sentence by simply replacing the underscores with spaces. Most of the times these sentences get really long and this is also the cause why phpspec test files will not fullfil any coding style guideline. But thats a cheap tradeoff for having good test descriptions.

A simple example

In this example I want to test a so called UserService. It should be able to fetch data from a repository and return the repository data stored in a simple DTO.

For this demonstration let’s assume the following boundaries:

<?php

class UserServiceSpec extends ObjectBehavior {

  function let(UserRepository $userRepository)
  {
    $this->beConstructedWith($userRepository);
  }

  function it_should_return_a_user_if_a_record_is_found_in_database(UserRepository $userRepository)
  {
    $userRepository->get(1)->willReturn([
        'id' => 1,
        'firstname' => 'John',
        'lastname' => 'Doe',
      ])->shouldBeCalled();

    $response = $this->get(1);

    $response->shouldHaveType(User::class);
    $response->id->shouldBe(1);
    $response->firstname->shouldBe('John');
    $response->lastname->shouldBe('Doe');
  }

  function it_should_throw_an_exception_if_no_user_data_is_found_in_database(UserRepository $userRepository)
  {
    $userRepository->get(1)->willReturn(null)->shouldBeCalled();

    $this->shouldThrow(NoSuchUserException::class)->duringGet(1);
  }
}

As you can see the syntax is really straightforward and you have sometimes many ways to reach on goal (you can replace shouldBe with shouldReturn and will get the same result). For more details about the phpspec syntax, other matchers or more details about the toolset please visit the projects homepage.

Wrap-Up

phpspec is the best choice for testing at the unit level for me, it binds the specified behavior (in the test case) to the real implementation. This is often treated as a bad practice but I really don’t think that this binding is a real problem because you have this specification-implementation binding also with the textual specification. If the specification changes also the implementation needs to change and vice versa.

Give it a try and after a short time of learning I’m sure you will love the power of it. Because it is one of the most important and difficult topics in the programming sector I will definitely write one or more phpspec related articles about phpspec.

If you have any questions regarding phpspec or find a bug or typo in this article feel free to contact me on Twitter. Comments, improvements and suggestions would be really appreciated.

Resources

This might be also interesting

Do you enjoy reading my blog?

sponsor me at ko-fi