Motivation
In this step you will learn how to write a façade using TDD (Test Driven Development).
The role of a Façade can be defined as follows:
- A Façade provides business level methods to the client, hiding any implementation details of services
- It forwards calls to the appropriate services
- and packs data returned from the services into a data transfer object.
The following diagram shows how StadiumFacades interacts with other elements used in the trail:
In this trail step, we create an integration test for the Stadium Façade,
resolve any potential problems that may arise and finally make the test pass.
Background on Façades
The intent of a Façade is to "Provide a unified interface to a set of interfaces in a subsystem. The Façade pattern defines a higher-level interface that makes the subsystem easier to use.": see The Façade Design Pattern. In our case, the Façade is the front-most API to which the client (web-pages) has access.
Consider for example a rich client in which the communication between the client and server should be kept to a minimum. If the client needs data from methodA in ServiceA, methodB in serviceB and methodC in serviceC, it would be more efficient for the client to be able to make one call to a Façade on the server that itself calls those 3 methods on the 3 services, rather than to call the 3 services itself. And since the Façade is making the calls, we can also ask the Façade to package the particular data we need (which might be somewhat duplicated in the 3 return values from the 3 services, or might not yet be complete), and to pass that back to the Client. This is the purpose of the Data Transfer Object: see Transfer Object Design Pattern and Data Transfer Object Assembler. Our façade will be performing both roles itself. You might choose to have a separate DTOAssembler in your own code (in accordance with the design principle Separation of Concerns), and we may modify this trail to also do that.
Create the data transfer objects
We create the Data objects in a declarative way, i.e. define beans and enumerations in an xml file used as input for code generating.
The main advantage is that you can merge attributes over several extensions - in the same way as it is possible with type definitions.
By doing so you make your façade layer easily extensible.
cuppytrail/resources/cuppytrail-beans.xml
<bean class="de.hybris.platform.cuppytrail.data.MatchSummaryData">
<description>Data object for MatchSummary which has no equivalent on the type system</description>
<property name="guestTeam" type="String"/>
<property name="homeTeam" type="String"/>
<property name="homeGoals" type="String"/>
<property name="guestGoals" type="String"/>
<property name="date" type="java.util.Date"/>
<property name="matchSummaryFormatted" type="String"/>
</bean>
<bean class="de.hybris.platform.cuppytrail.data.StadiumData">
<description>Data object representing a Stadium</description>
<property name="name" type="String"/>
<property name="capacity" type="String"/>
<property name="matches" type="java.util.List<de.hybris.platform.cuppytrail.data.MatchSummaryData>"/>
</bean>
2.
Run
ant clean all
and refresh the workspace. (ant all is OK)
Have a look at the created data object classes:
StadiumData
and MatchSummaryData
.
Write the interface
Create the interface:
package de.hybris.platform.cuppytrail.facades;
import de.hybris.platform.cuppytrail.data.StadiumData;
import java.util.List;
public interface StadiumFacade
{
StadiumData getStadium(String name);
List<StadiumData> getStadiums();
}
Key points to note:
- the interface is similar to the StadiumService, with the key difference that
Write the implementation
cuppytrail/src/de/hybris/platform/cuppytrail/facades/impl/DefaultStadiumFacade.javaAnd in cuppytrail's Spring configuration:
<alias name="defaultStadiumFacade" alias="stadiumFacade"/>
<bean id="defaultStadiumFacade" class="de.hybris.platform.cuppytrail.facades.impl.DefaultStadiumFacade">
<property name="stadiumService" ref="stadiumService" />
</bean>
Write the unit test
The integration test is useful for testing and demonstrating the expected behavior of any class that implements the StadiumFacade interface. But to test this particular implementation of the interface in detail we need a unit test that mocks out the classes on which the implementation depends. Again this is done with Mockito.
Copy the following code to cuppytrail/testsrc/de/hybris/platform/cuppytrail/facades/impl/DefaultStadiumFacadeUnitTest.java
/**
*
*/
package de.hybris.platform.cuppytrail.facades.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import de.hybris.platform.cuppytrail.StadiumService;
import de.hybris.platform.cuppytrail.data.StadiumData;
import de.hybris.platform.cuppytrail.model.StadiumModel;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
public class DefaultStadiumFacadeUnitTest
{
private DefaultStadiumFacade stadiumFacade;
private StadiumService stadiumService;
private final static String STADIUM_NAME = "wembley";
private final static Integer STADIUM_CAPACITY = Integer.valueOf(12345);
// Convenience method for returning a list of Stadium
private List<StadiumModel> dummyDataStadiumList()
{
final StadiumModel wembley = new StadiumModel();
wembley.setCode(STADIUM_NAME);
wembley.setCapacity(STADIUM_CAPACITY);
final List<StadiumModel> stadiums = new ArrayList<StadiumModel>();
stadiums.add(wembley);
return stadiums;
}
// Convenience method for returning a Stadium with code and capacity set for wembley
private StadiumModel dummyDataStadium()
{
final StadiumModel wembley = new StadiumModel();
wembley.setCode(STADIUM_NAME);
wembley.setCapacity(STADIUM_CAPACITY);
return wembley;
}
@Before
public void setUp()
{
// We will be testing the POJO DefaultStadiumFacade - the implementation of the StadiumFacade interface.
stadiumFacade = new DefaultStadiumFacade();
/**
* The facade is expected to make calls to an implementation of StadiumService but in this test we want to verify
* the correct behaviour of DefaultStadiumFacade itself and not also implicitly test the behaviour of a
* StadiumService. In fact as of writing this class, we do only have the interface StadiumService and no
* implementation. This requires that we mock out the StadiumService interface. There are several strong arguments
* for following this practice:
*
* If we were to include a real implementation of StadiumService rather than mocking it out..
*
* 1) we will not get "false failures" in DefaultStadiumFacade due to errors in the StadiumService implementation.
* Such errors should be caught in tests that are focusing on StadiumService instead.
*
* 2) The condition could arise where an error in the facade gets hidden by a complimentary error in the
* StadiumService implementation - resulting in a "false positive".
*
* By mocking out the interface StadiumService..
*
* 3) we do not actually need an implementation of it. This therefore helps us to focus our tests on this POJO
* before having to implement other POJOs on which it depends - allowing us to write tests early.
*
* 4) by focusing on the behaviour of the facade and the interfaces it uses, we are forced to focus also on the
* those interface, improving them before writing their implementation.
*
*
* Therefore we create a mock of the StadiumService in the next line.
*/
stadiumService = mock(StadiumService.class);
// We then wire this service into the StadiumFacade implementation.
stadiumFacade.setStadiumService(stadiumService);
}
/**
* The aim of this test is to test that:
*
* 1) The facade's method getStadiums makes a call to the StadiumService's method getStadiums
*
* 2) The facade then correctly wraps StadiumModels that are returned to it from the StadiumService's getStadiums
* into Data Transfer Objects of type StadiumData.
*/
@Test
public void testGetAllStadiums()
{
/**
* We instantiate an object that we would like to be returned to StadiumFacade when the mocked out
* StadiumService's method getStadiums is called. This will be a list of two StadiumModels.
*/
final List<StadiumModel> stadiums = dummyDataStadiumList();
// create wembley stadium for the assert comparison
final StadiumModel wembley = dummyDataStadium();
// We tell Mockito we expect StadiumService's method getStadiums to be called, and that when it is, stadiums should be returned
when(stadiumService.getStadiums()).thenReturn(stadiums);
/**
* We now make the call to StadiumFacade's getStadiums. If within this method a call is made to StadiumService's
* getStadiums, Mockito will return the stadiums instance to it. Mockito will also remember that the call was
* made.
*/
final List<StadiumData> dto = stadiumFacade.getStadiums();
// We now check that dto is a DTO version of stadiums..
assertNotNull(dto);
assertEquals(stadiums.size(), dto.size());
assertEquals(wembley.getCode(), dto.get(0).getName());
assertEquals(wembley.getCapacity().toString(), dto.get(0).getCapacity());
}
@Test
public void testGetStadium()
{
/**
* We instantiate an object that we would like to be returned to StadiumFacade when the mocked out
* StadiumService's method getStadium is called. This will be the StadiumModel for wembley stadium.
*/
// create wembley stadium
final StadiumModel wembley = dummyDataStadium();
// We tell Mockito we expect StadiumService's method getStadiumForCode to be called, and that when it is, wembley should be returned
when(stadiumService.getStadiumForCode("wembley")).thenReturn(wembley);
/**
* We now make the call to StadiumFacade's getStadium. If within this method a call is made to StadiumService's
* getStadium, Mockito will return the wembley instance to it. Mockito will also remember that the call was made.
*/
final StadiumData stadium = stadiumFacade.getStadium("wembley");
// We now check that stadium is a correct DTO representation of the ServiceModel wembley
assertEquals(wembley.getCode(), stadium.getName());
assertEquals(wembley.getCapacity().toString(), stadium.getCapacity());
}
}
Key notes:
- By mocking out the service on which the façade depends, we are able to test the façade in isolation.
- With Mockito, we can mock out the service, and verify that the expected calls are made from the façade to the service interface.
- These tests are not just useful when writing the façade, but when maintaining it on an ongoing basis - they remain in the test suite, and run as part of Continuous Integration.
- The Cuppy Façade Tests listed below are a good resource for learning TDD best practices.
- cuppy/web/testsrc/de/hybris/platform/cuppy/web/facades/DefaultMatchFacadeTest.java
- cuppy/web/testsrc/de/hybris/platform/cuppy/web/facades/DefaultPlayerFacadeTest.java
- cuppy/web/testsrc/de/hybris/platform/cuppy/web/facades/DefaultStatisticsFacadeTest.java