6

In my application I have quite a few service classes that act as a façade and delegate most calls to one or more underlying manager classes. I've read very different opinions on how to test such façades. Some people say it's not necessary to test them at all, others say one should test whether the integration is working.

I've implemented two test methods for the delegating method below. One test is checking whether the underlying manager method has been called exactly once, the other test checks whether the passed data is equal (or in this case: the same).

@Stateless
public class ApplicationService {

    @Inject
    ApplicationManager applicationManager;

    public List<Application> getApplicationsList() {
        return applicationManager.getApplications();
    }

    ...
}

@RunWith(CdiRunner.class)
public class ApplicationServiceTest {

    @Produces
    @ProducesAlternative
    @Mock
    ApplicationManager manager;

    @Inject
    ApplicationService service;

    @Test
    public void testGetApplicationsListCalled() {
        service.getApplicationsList();
        verify(manager, times(1)).getApplications();
    }

    @Test
    public void testGetApplicationsListPassthrough() {
        ArrayList<Application> given = new ArrayList<>();
        given.add(new Application("app1", "desc1", BusinessCriticality.C1));
        given.add(new Application("app2", "desc2", BusinessCriticality.C1));
        when(manager.getApplications()).thenReturn(given);

        List<Application> resulting = service.getApplicationsList();

        Assert.assertEquals(given, resulting);
        Assert.assertEquals(2, resulting.size());
    }
}

I'd love to hear opinions from experienced devs on whether testing such methods makes sense and if it does, which method of testing is better.

Tulains Córdova
  • 39,201
  • 12
  • 97
  • 154
  • I would say the real problem here is having those "facades" in the first place. A proper "Facade" (as described in the Design Patterns/GoF book) exists to provide a simpler, smaller API to a pre-existing external API which is more complex than what the application needs. When it's just a do-nothing class containing methods that simply delegate to another application class, then it's an anti-pattern. Eliminate it and then the issue of testing the facade will also go away. – Rogério Nov 01 '16 at 18:58
  • Hi @Rogério and thanks for the answer. Feel free to make it a real answer (instead of comment). I agree in that most classes in my layers system do nothing but passing things around, but I found that to be the only way of creating reliable packages and interfaces. For example, my *Manager classes contain lots of logic and many methods meant for other managers, whereas the *Service classes are a filtered view on the managers. My view classes only access the *Service classes. This layer-based abstraction gives me more flexibility than Java interfaces. – Hubert Grzeskowiak Nov 02 '16 at 09:08

2 Answers2

3

You have to distiguish between whitebox tests and blackbox tests.

As blackbox tests do not know, how a component behaves, you have no clue if the called method is only a delegate or complex logic. As well you abstract from knowing which resources will be accessed so any potential resource has to be available a test runtime (unless you know to mock some resources). As you do not know a lot of things, you have assume the worst. So you should test every method. Black box test tend to be integration tests.

As the code provided is code under your control you know what the code is doing whitebox tests are appropriate. So you know if a method is only a delegate. These methods should not be tested as these tests express nothing. What you should do is to test if the object which methods are delegated to is properly initialized (null reference exceptions). If you now test the method of the delegate-object you are done because you know delegation can be process as the object is available and the delegated method works fine.

Unit tests for a method begin with at least TWO statements as with a second statement you maybe introduce first interesting thing to test: call order.

The next structure you should be aware of are conditional statements. You should try to refactor methods to have only one level of conditions. The problem with conditions is they may lead to a test case explosion if you have too much levels and conditional elements. Beside test possibility: To have small methods is always a good idea.

oopexpert
  • 769
  • 4
  • 7
  • Thanks for the detailed answer. So basically you say I should write a test for my service that checks whether the manager was properly injected? – Hubert Grzeskowiak Nov 02 '16 at 09:31
  • White boxing testing for facades can have tests for error handling also. Tests to make sure that the facade is handling all the exception that can be thrown by the service client. – IThinkSo Oct 16 '17 at 10:19
0

From best practices perspective keep unit tests (JUnits + mock) separate from integration tests. You want unit tests to run independent of any system resources (barring any test data resource files). You can either mock service.getApplicationsList() or implement a factory pattern that will return service.getApplicationListFromTestImpl() in your first method to work with test data.

Integration tests should cover all system resources.

donlys
  • 117
  • 3
  • 2
    Sorry, but this answer doesn't help me in any way. The test above does run independently of any system resources and it doesn't involve more than one class, so I'd say it's a unit test. However, this is not my question. You say, I should mock the service method `getApplicationsList()` - what do I gain by mocking a method I want to test? Same goes for the factory. I don't want to change the implementation except where necessary. I don't feel like introducing a factory improves the testability of this class. – Hubert Grzeskowiak Nov 02 '16 at 09:15
  • service.getApplicationsList() is making a service call - correct? If so, then these are not unit tests and these are integration tests. The ideal way to unit test getApplicationList() is to go to the core implementation that retrieves the lists. If lists are coming from db, etc., write a test data provider class - so that the unit test gets test data, perform whatever logic/checks it needs to, and then returns the list to the external caller. – donlys Nov 02 '16 at 13:29
  • If you are writing integration tests - then technically you don't need to have the second test to compare the list size - all you really want to test is that it is returning some data and so confirming that integration is working. – donlys Nov 02 '16 at 13:30
  • `service.getApplicationsList()` is what the view code uses (backing beans to be precise). The service is part of a layer that sits between the view related code and the lower-level managers. If I get you correctly, you say I shouldn't test that service at all, but only the underlying manager, right? – Hubert Grzeskowiak Nov 02 '16 at 13:48
  • A unit test tests a method. Since you are not unit testing getApplicationsList() - yes - you just need one IT. – donlys Nov 02 '16 at 14:10
  • Okay, and how would such a IT look like? Mocking the data layer and testing with the real manager and service? – Hubert Grzeskowiak Nov 02 '16 at 14:22
  • List resulting = service.getApplicationsList(); and resulting != null && resulting.size() > 0 is a good IT test. You don't mock anything for IT tests - as name states IT is to ensure that two or more components are correctly integrated in a runtime-like environment. – donlys Nov 02 '16 at 14:26
  • You need to mock something, in the end, and let alone the database (using dbunit). – Hubert Grzeskowiak Nov 02 '16 at 14:31
  • Not for integration tests. Ensuring that there is connectivity between your manager layer and db is also part of the integration test. If you mock let's say your db - how do you know that the db has right schema/stored procedures, etc needed by your manager layer? These are the things that integration tests are also supposed to discover. – donlys Nov 02 '16 at 14:35
  • I disagree. Integration tests must not span throughout the whole system. The wider the scope of your tests, the harder are errors to pinpoint. What you are talking about are system tests (in sense of smoke tests). My own tests are not supposed to test a DBMS. Whether my queries and schemas are correct is tested in the DAO tests. If the DAOs are correct (dbunit) and the DAOs are properly integrated within the managers (IT), I don't need an impossible-to-maintain test for the whole thing. – Hubert Grzeskowiak Nov 02 '16 at 15:32
  • You are correct - integration testing is always within the context of your scope. If I am consuming a third party API - I don't care what happens inside that implementation and will do IT only for the contact it is exposing and using test data it provides. In your case only you know what is your domain/scope. – donlys Nov 03 '16 at 16:45