Search code examples
phpunit-testinglaravelphpspec

phpspec unit testing - using ioc / service registry for delivering the concrete class to test


I am new to testing and I'm not sure I go about this the right way:

I want to not to do a unit test on a specific class but on whatever class get resolved out of my ioc container. In the ioc container I bind my interfaces to concrete classes, like so:

Example (I'm using Laravel 5):

// in a service provider
public function register(){ 

    $this->app->bind('FooInterface', function() { 
        return new SomeConcreteFoo; 
    }); 
 }

Then I want to write unit test against FooInterface and not SomeConcreteFoo which could be swapped out with some other class at a later point.

The reason I want to do this is that it seems to me that the relevant testing should target whatever my ioc container returns, since that is what I'll be using in the application. It also seems to me that the testing should be done on the interface level, since that is where I define the expectations the rest of the app will have to my class.

I'm having a hard time finding any information on how to do this, which suggests to me that I might think about this the wrong way. For instance maybe what I'm trying to accomplish is more like an integration test rather than a unit test.

So the first question is: am I thinking about testing this the right way? in case I'm not - do you have any suggestions regarding best practice for an alternative test path.

The second question: In case my thinking is sound - how do I setup phpspec to make use of the Laravel IOC container, so that I can test against whatever the IOC returns..


Solution

  • I want to not to do a unit test on a specific class but on whatever class get resolved out of my ioc container. In the ioc container I bind my interfaces to concrete classes, like so [...]

    This is not how unit tests are written. Unit testing is about describing a class behaviour in isolation, so the only real object actually created is the class under test (and sometimes simple value objects).

    Then I want to write unit test against FooInterface and not SomeConcreteFoo which could be swapped out with some other class at a later point.

    This is indeed how you should write your unit tests. Prefer interfaces for collaborators.

    Each mocking framework supports this feature, and will create test doubles for you, without forcing you to provide specific implementations.

    class BarSpec extends ObjectBehavior
    {
        function it_does_amazing_things(FooInterface $foo)
        {
            $results = ['a', 'b', 'c'];
    
            $foo->find('something')->willReturn($results);
    
            $this->findMeSometing()->shouldReturn($results);
        }
    }
    

    In this particular example, PhpSpec will use Prophecy (its mocking framework), to create a test double of FooInterface and will inject it to the example method. What you do with this object determines if it's a fake, stub or a mock.

    The reason I want to do this is that it seems to me that the relevant testing should target whatever my ioc container returns, since that is what I'll be using in the application.

    As explained above, unit tests focus on behaviour of a single class. Its collaborators are usually faked. This is for several reasons. One of them is speed. Another one is feedback. If a test fails we'll get a clear feedback on which class is broken. If you were creating all collaborators, instead of using test doubles, a single bug could make your whole test suite red. I won't even mention how hard it would be to maintain and create all the needed objects (although containers could help here).

    Remember that writing unit tests is more a design activity, rather than a testing activity.

    For instance maybe what I'm trying to accomplish is more like an integration test rather than a unit test.

    Indeed. Read more about test pyramid. Most of your tests should be unit tests. Then, you should have some number of integration and acceptance tests, which would exercise more than a single class at once. The reason why you'd want more unit tests than integration tests is that the later are more fragile and it's harder to maintain/change them.

    Use PHPUnit for integration tests. PhpSpec is not the right tool for this job. PhpSpec is great for designing your classes (writing unit tests), especially if you do it test-first.

    The second question: In case my thinking is sound - how do I setup phpspec to make use of the Laravel IOC container, so that I can test against whatever the IOC returns..

    You don't. You could think of using the container in integration tests though.

    Some reading: