How to avoid brittle tests when testing the service layer

There are many ways to test the service layer of an application. The goal here is to show how to unit test this layer in isolation, by mocking out the interactions with the database entirely.

This example will use Spring 3 for the dependency injection, JUnit, Hamcrest and Mockito for testing, but the technologies can vary.

The layers

The typical java web application will havea service layer, on top of a DAL/DAO layer which in turn will calls the persistence layer.

The Service Layer:

View the code on Gist.

The DAL/DAO layer:

View the code on Gist.

Motivation and blurring the lines of the unit test

When unit testing a service, the standard unit is usually the service class, simple as that. The test will mock out the layer underneath – in this case the DAO/DAL layer and verify the interactions on it. Exact same thing for the DAO layer – mocking out the interactions with the database (HibernateTemplate in this example) and verifying the interactions with that.

This is a valid approach, but it leads to brittle tests – adding or removing a layer almost always means rewriting the tests entirely. This happens because the tests rely on the exact structure of the layers, and a change to that means a change to the tests.

To avoid this kind of inflexibility, the scope of the tests will grow from the single service class to all the layers. Now, when testing the service layer, it’s not the layer below that will be mocked, but the final layer which is directly interacting with the database – in this case, the HibernateTemplate:

View the code on Gist.

Now the test only focuses on a single responsibility – when creation is triggered, does the creation reach the database?

The last test uses Mockito verification syntax to check that the save method has been called on the hibernate template, capturing the argument in the process so that it can be checked as well. The responsibility of creating the entity is verified via this interaction test, without the need to check any state – the test trusts that the hibernate save logic is working as intended. Of course that needs to be tested as well, but that is another responsibility and another type of test.

Conclusion

This technique invariably leads to more focused tests, which makes them more resilient and flexible to change. The only reason the test should now fail is because the responsibility under test is broken.

If you read this far, you should follow me on twitter here.

, ,

  • http://www.alex-johnson78.com Alex

    Can I just say what a relief to find someone who actually knows what they’re talking about on the internet. You definitely know how to bring an issue to light and make it important.

    More people need to read this and understand this side of the story. I cant believe you’re not more popular because you definitely have the gift.

    Best regards Alex

  • http://www.topellipticalmachinereviews.com/horizon-fitness-ex-59/ horizon fitness elliptical machine reviews

    This is the exact info i’m looking for, thanks! Arron

  • sMoZely

    Nice blog, like the pragmatic approach rather than the dogmatic view that “unit tests must test only one class” … which I keep having to beat out of people.

    Need to make sure tests provide value, and break when mistakes are made … rather than just being re-written each time you make a code change.

    But just to touch on one thing that follows from my previous point … do you have separate tests that prove your DAO/DAL layer acts as expected?
    Just my experience, is that the parts where we have bugs are where hibernate doesn’t work as expected (not bugs, just its a big an often subtle API). So I’ve generally tried to get people to focus on good testing there.

    • Anonymous

      You touch on some interesting points here – yes, the concept of test per class is a good constraint to follow very early on, and a good tool to learn. It is however an unnecessary constraint in production. I find that behavior is more fit to represent what it is that I’m testing, rather than unit, and clearly a behavior can be achieved by more than one class.
      Regarding integration testing, yes, it’s indispensable – these unit tests will not exercise the persistence layer and are not meant (or fit) to catch the particular class of problems, which is why you need integration tests as well.
      Thanks for the interesting feedback.

  • Rafael Ponte

    Well, I can’t agree with you. Writing unit tests this way doesn’t mean your tests are lesser brittle then mocking DAO layer, they still continue being brittle tests.

    In my experience, trying to mock third-party API’s is such a problem most of the times. For example, mocking EntityManager, Hibernate Session or even worse, the Hibernate Criteria is rarely a good idea. Normally, for service layer (that usually touches persistence layer) I write integration tests, they are more concise and most of cases simple to write, so when you have a integration tests you really don’t need to write unit ones to the same class.