Agile Zone is brought to you in partnership with:

I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 635 posts at DZone. You can read more from them at their website. View Full User Profile

Practical PHP Patterns: Service Stub

10.13.2010
| 7054 views |
  • submit to reddit

When I first learn about the Service Stub pattern, I heard a smell of unit testing ante litteram: it is indeed a technique which has been generalized over the years to the Stub testing pattern, which was originally implemented due to necessity.

Imagine you have a system that accesses a third-party service, which would nowadays be a web service, but also a LDAP server or a geographic information system.

This external resource will be often too slow for effective testing: its direct usage would involve input/output, sending packets over the network, conversions of data, chatty traffic between the machine that run the test and the server, hard dependency over the availability of the service or of a copy of it. If you compare this with fast, easy to setup, in-memory tests, you see how the approach of referring to the real service in testing is not always available, and leads to tests that cease to be executed due to their brittleness and slowness.

Rationale

The solution for conversing with an external service in our object-oriented application is to wrap the service with a Gateway interface, which is implemented by a RealGateway class or component that is used to access the external resource.

The Gateway is a thin layer of abstraction, which exposes only the features you need from the external system. the importance of a Gateway is in what it hides: whatever is not accessed, preventing its complexity to fall on us.

After you have a Gateway in place, or even while you're producing it, you can introduce a Service Stub implementation that you can use for testing. Your objects will use the Service Stub instead of the Real Gateway in their testing environment.

With a Service Stub dedicated to testing, you can easily reproduce all the scenarios you want: particular data coming from the system, or fake data (easier to reproduce), or even error conditions.

In the case of web service, I'm not a fan of mocking the original interface (by calling for example PHPUNit's getMockFromWsdl()), as we should try to reduce our contract with the service to the most cohesive and little possible one for the application to work. By the way, most services don't even have an available WSDL description.

The Only mock types you own principle describes mocking and stubbing as design tools, so the wrapping interface should be free of change when the client code dictates so. If you use the original interface provided, you let the implementor design influence your code.

This often is reflected at the moment of building stubs: when you have your interface, you're really fine as you can tweak it to make it easy to test (which reflect on its decoupling from the external service.) When you have a provided interface like a WSDL-described service, it often becomes too difficult. As usual, tests that are difficult to write tell us to improve our design.

Testing methods

What kind of testing do you exercise the code implementing this pattern with?

Integration testing on the Real Gateway. Test that it uses correctly the service, this would be a minority and will use the real service implementation. This kind of test can be skipped if the implementation is not available, but they must be run by Continuos Integration.

Unit testing on client classes with the Stub, which is test-friendly. These are the tests you use as a design tool, so it's critical that they run quickly to give you feedback. Here you do not have any dependency on the external service: you can run these tests on your laptop on the train.

Examples

We'll expand an example from the Gateway article, since the two patterns are very related: the Service Stub ease of creation depends on the existence of a good Gateway.

We'll create a very simple Stub: a of subclass of the TwitterGateway that provides always the same tweet. Since our tests are very focused, an hardcoded tweet should be enough for now. I'm coding it manually here, but also showing how PHPUnit would do the task.

Normally, when creating it with PHPUnit seems too difficult, you are probably putting too much logic in the Gateway: you should divide it in a simpler Gateway and then build over it a set of services you can test in isolation with a new, segregate Service Stub.

<?php
/**
 * The Real Gateway. For simplicity, there is no 
 * interface here but the Service Stub simply subclass the Real Gateway.
 */
 *
class TwitterGateway
{
    /**
     * the only functionality I need from the feed
     */
    public function getLastTweet($username)
    {
        $endPoint = "http://twitter.com/statuses/user_timeline/{$username}.xml?count=1";
        $buffer = file_get_contents($endPoint);
        $xml = new SimpleXMLElement($buffer);
        return $xml->status->text;
    }
}

/**
 * Hand-rolled Service Stub.
 */
class TwitterGatewayStub
{
    /**
     * You may provide setters or other specification methods
     * in case you have much more data to return.
     * Usually putting it in the constructor is not a problem, since in
     * every test we will inject a new Service Stub in a new client object.
     * Feel free to use setters if you suddenly have to change what is returned.
     */
    public function __construct($tweetToReturn)
    {
        $this->text = $tweetToReturn;
    }

    /**
     * An advantage of PHPUnit mocks and stubs is that they can check parameters
     * passed to methods. We can do it too, but it will be less clear
     * in case of failure.
     * By the way, the definition of the Stub pattern (specialization
     * of Test Double) is a substitute that does not check the parameters
     * but only return canned results. With this assertion this class is 
     * a Mock, but Fowler's original name remains.
     */
    public function getLastTweet($username)
    {
        assert('$username == "giorgiosironi"');
        return $this->text;
    }
}

class SomeClientClassUnitTest extends PHPUnit_Framework_TestCase
{
    public function testSomeFeatureOfTheClientClass()
    {
        // automatically generated Service Stub
        $twitterMock = $this->getMock('TwitterGateway');
        $twitterMock->expects($this->once())
                    ->method('getLastTweet')
                    ->with('giorgiosironi')
                    ->will($this->returnValue('A Tweet...'));

        // or
        // $twitterMock = new TwitterGatewayStub('A Tweet');

        // $client = new ClientClass($twitterMock);
        // proceed with the test...
    }
}
Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)