PHP Integration Testing – Getting Started After Unit Testing

PHPunit testing

I've written a class that manages recipients on a MailChimp list, called MailChimpRecipient. It uses the MCAPI class, which is an third-party API wrapper.

http://apidocs.mailchimp.com/api/1.3/
http://apidocs.mailchimp.com/api/downloads/

I pass the MCAPI object into the constructor of the MailChimpRecipient object, so I have written unit tests using PHPUnit that test all of the logic in my own class (I am not testing the MCAPI class). I have 100% code coverage and all tests pass. This is done by mocking and stubbing the MCAPI object.

My next step was to write an integration test, also using PHPUnit, where I would construct the MailChimpRecipient fixture using a real MCAPI object, set up to use a real MailChimp list.

I have written what I think is an intergration test, which basically runs tests agains the public interface of the object, like:

public function testAddedRecipientCanBeFound()
{
    $emailAddress = 'fred@fredsdomain.com';
    $forename = 'Fred';
    $surname = 'Smith';

    // First, delete the email address if it is already on the list
    $oldRecipient = $this->createRecipient();
    if($oldRecipient->find($emailAddress))
    {
        $oldRecipient->delete();
    }
    unset($oldRecipient);

    // Add the recipient using the test data
    $newRecipient = $this->createRecipient();
    $newRecipient->setForename($forename);
    $newRecipient->setSurname($surname);
    $newRecipient->setEmailAddress($emailAddress);
    $newRecipient->add();
    unset($newRecipient);

    // Assert that the recipient can be found using the same email address
    $this->assertTrue($this->_recipient->find($emailAddress));
}

The "integration" test doesn't test any of the internals of the class – it just makes sure that given a real MCAPI object, it behaves as advertised.

Is this correct? Is this the best way to run an intergation test? After all, the internals have been tested with a unit test. Am I right in thinking that the integration test is there to test that it really works, according the way its behaviour is advertised?

To take it a step further, the MailChimpRecipient class implements an interface, which will also be implemented by other classes. The idea is to use a factory to pass different types of mailing list recipient objects to my code, which all do the same thing, albeit using different mailing list providers. Since my integration tests test the that interface, how about using it for all of the classes that implement the interface? Then, in the future, if I design a new class to be used interchangably, I can run the same integration test before inserting it into a project.

Does this sound reasonable? The unit tests test the internals of an object, intergration tests make sure that it behaves as advertised?

Best Answer

When testing your code you should pay attention to three areas:

  • Scenario testing
  • Functional testing
  • Unit testing

Normally the amount of test you have in each category would have the shape of a pyramid, meaning a lot unit tests at the bottom, some functional tests in the middle, and just a few scenario tests.

With a unit test you mock out everything that the class under test uses and you test it in pure isolation (this is why it's important to make sure that inside your class you retrieve all dependencies trough injection so they can be replaced under test).

With unit testing you test all possibilities, so not only the 'happy path' but also all error conditions.

If you are completely certain that your all units work in isolation you write a couple of tests (functional tests) to make sure the units also work when combined. Then you write a scenario test, which tests the wiring between all the functional modules.

For example, suppose you're testing a car.

You could assemble the whole car and as a driver check every possible condition but that would be really hard to do.

Instead you would test a small part of the engine with all possibilities (unit test)

Then you test the whole engine (separate from the car) which would be a functional test.

As a last test, you put your key in, start the car and drive it to the parking lot. If that's working then you know that all parts (battery, fuel, engine, ..) are connected and since you tested them in isolation you can be pretty sure that the whole car is functioning correctly.

So in your case, you tested all error conditions and the happy path in your unit test and know you only have to an end-to-end test with the 'real components' to check if the wiring is correct.

A few other points,

  • Avoid conditional logic in your unit test. If you have to clean up, your using some kind of global state and the tests can suddenly influence each other.
  • Don't specify any data that's not relevant for the test. If I would change forename, or surname, would the test fail? Probably not because it's the email address that's important but because you mention it explicitly in your test, I can't be sure. Try looking at the Builder Pattern for building your test data and making it explicit what's really important.
Related Topic