blog.smart-java.nl
Ordina J-Technologies – Java Blog



Spring en EasyMock

By: Ron Thijssen, 24 April 2008

Unit tests hebben zichzelf allang bewezen. Ze zijn erg krachtig doordat ze in een korte cyclus feedback kunnen geven over de kwaliteit van je code. Door continuous integration pakketten in te zetten, word je nog eens persoonlijk erop gewezen dat de code wijziging die je zojuist doorgevoerd hebt geen nieuwe fouten heeft geïntroduceerd. Ontwikkelaars maken immers nooit fouten :-)

Zoals het hoort plaatst iedere ontwikkelaar zijn business logic netjes in services en de data acces komt netjes in Data Access Objects. In dit voorbeeld wil ik laten zien hoe je op functioneel niveau je code kan testen zonder dat hier data access voor nodig is. Dus met de constraint dat de code niet met de database mag communiceren. Aangezien we framework freaks zijn en we allemaal Spring gebruiken vanwege dependency injection en het assembleren van objecten gaan we dit ook toepassen in onze testcases. We hebben onze bean definities namelijk al tot onze beschikking. Hierin staan bijvoorbeeld services, dao’s, iterators, processors.

We beginnen even met een voorbeeldje van een testcase.

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations={"classpath:services.xml", "classpath:data.xml"})

public class ServiceTest extends TestCase{

@Autowired

private LoginService loginService;

@Test

public void login() {

User result = loginService.login("Test", "Test");

assertEquals(result.getUsername(), "Test");

}

}

Op dit moment krijgt de LoginService een HibernateUserDaoImpl geïnjecteerd om mee te communiceren. Maar omdat we geen data access willen moeten we deze UserDao vervangen. We komen helaas niet weg met het maken van een DAO-stub. Voor UPDATE en INSERT statements zou het eventueel kunnen, maar zodra een query een bepaalde resultset terug moet geven hang je. We moeten dus een nieuwe UserDao maken met afwijkend gedrag. Kortom; mocken. Nu kunnen we zelf een nieuwe TestUserDaoImpl maken, maar dit introduceert een extra class per testcase (mits we het gedrag van de TestUserDaoImpl willen kunnen veranderen per testcase.) Een betere optie is om gebruik te maken van het mocking framework EasyMock.

Middels EasyMock kunnen we eenvoudig mock objecten maken en declaratief van gedrag voorzien. Het instantiëren van een mock object is relatief eenvoudig:

UserDao userDao = EasyMock.createMock(UserDao.class);

vervolgens kunnen we met expect scripts het gedrag van de userDao aanpassen. Vervolgens zullen we handmatig deze userDao in onze loginService moeten injecteren….

Maar laat Spring daar nu juist net zo goed in zijn. Je raadt het al; we gaan Spring gebruiken om mock objecten te injecteren.

We beginnen met het maken van een bean configuratie die we enkel voor test gaan gebruiken:

<bean id="userDao" class="org.easymock.EasyMock"

factory-method="createMock">

<constructor-arg value="nl.ordina.dao.UserDao" /> </bean>

Bovenstaande bean configuratie maakt gebruik van de factory-method argument, waardoor Spring objecten kan instantiëren middels een factory method. In dit geval is dat EasyMock.createMock(Class<T> class); Als constructor-argument hoeven we enkel de fully qualified classname mee te geven. Spring zal automatisch deze waarde parsen naar een Class. Omdat de UserDao een interface is gebruiken we org.easymock.EasyMock. Indien je een class wil mocken gebruik dan de classextension package van EasyMock.

Als we nu naar onze testcase gaan kijken ziet deze er als volgt uit.

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations={"classpath:services.xml", "classpath:test-data.xml"})

public class ServiceTest extends TestCase{

@Autowired

private LoginService loginService;

@Autowired

private UserDao userDao;

@Test

public void login() {

User result = loginService.login("Test", "Test");

assertEquals(result.getUsername(), "Test");

}

}

De @ContextConfiguration annotatie hebben we aangepast om gebruik te maken van onze test-data.xml configuratie in plaats van de data.xml. Vervolgens laten we Spring de userDao injecteren.

Vervolgens kunnen we het gedrag van de userDao gaan specificeren.

@Before

public void mockSetup() {

User testUser = new User("Test", "User");

expect(userDao.findBy(isA(String.class), isA(String.class))).andReturn(testUser).once();

replay(userDao);

}

@After

public void mockRest() {

reset(userDao); }

Ik gebruik de @Before en @After annotaties van JUnit 4 welke respectievelijk voor en na de testcase aangeroepen worden. We laten userDao.findBy() nu een vooraf gedefinieerde User retourneren welke we kunnen gebruiken om de daadwerkelijk functionaliteit van de overige services te testen.
Een aantal opmerkingen:

  • Let op bij het gebruiken van scope=”prototype” voor de MockBeans. Alle beans die hier gebruik van maken krijgen ieder een nieuwe instantie, zonder gedrag.
  • Gebruik een tearDown method om de mockobjecten te resetten. Andere beans zullen tegen dezelfde mock aanpraten met het door jouw gespecificeerde gedrag.

Happy testing!

Eén reactie op “Spring en EasyMock”

  1. Jan-Kees van Andel zegt:

    Op deze manier krijg je idd wel lekker korte test cases. Het vervelende van EasyMock vind ik dat je altijd zoveel setUp code nodig hebt om je mocks op te zetten. Op deze manier kun je dat mooi weer wat verminderen.

Laat een reactie achter