Dnešní příspěvek bude velmi krátký. Je dost pravděpodobné, že podobné řešení už dávno máte ve svých tetovacích utilitkách, ale mě tato kombinace napadla relativně nedávno a jsem nadšený z toho, o jak elegantní řešení se pro testy jedná.
V některých testech potřebuji vytvořit část Spring aplikačního kontextu, jehož některé beany mají závislost na nějaké další beaně, kterou je pro mne obtížné do testu zahrnout. Buď z důvodu, že její samotné vytvoření s vyžaduje další komplexní infrastrukturu okolo ní nebo třeba proto, že její zařazení do testovacího kontextu způsobuje při běhu testu vedlejší efekty (např. odeslání e-mailu).
Pro tyto případy jsem si vytvořil jednoduchou Spring FactoryBean, která používá mockovací knihovnu Mockito a která vytvoří místo zmíněné obtížné beany, na kterou mám v kontextu závislosti, dynamickou proxy imitující její chování:
import org.springframework.beans.factory.FactoryBean;
import static org.mockito.Mockito.mock;
/**
* Simple factory bean creating mock object for specified class.
* Mock object is a prototype that means it is created
* everytime Spring bean retrieval occurs. In practice it means
* that each test method has its own new and pretty mock
* object created before it starts.
*/
public class MockitoFactoryBean implements FactoryBean {
private Class mockClass;
public void setMockClass(Class mockClass) {
this.mockClass = mockClass;
}
public Object getObject() throws Exception {
return mock(mockClass);
}
public Class getObjectType() {
return mockClass;
}
public boolean isSingleton() {
return false;
}
}
V úplně nejjednodušším případě mi potom stačí v kontextu místo původní beany definovat beanu zástupnou – díky níž umožním naběhnutí celého kontextu aniž bych musel nějak výrazněji šachovat se Spring konfiguráky:
<bean id="mailService" class="com.fg.support.test.MockitoFactoryBean">
<property name="mockClass" value="cz.novoj.mail.MailService"/>
</bean>
Druhé hezké použití tohoto přístupu je ve chvíli, kdy potřebujeme pro test nainstruovat chování konkrétní mockované beany. Díky tomu, že MockitoBeanFactory vytváří beany typu „prototype“ (tj. vždy, když požádáme kontext o referenci na danou beanu, vznikne nová instance konkrétní třídy), má každá testovací metoda na začátku svou vlastní „čistou“ instanci tohoto mocku, který už stačí jen naskriptovat:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath:/META-INF/spring/business.xml",
"classpath:/META-INF/spring/mocks.xml"
})
public class BusinessObjectTest {
@Autowired private BusinessObject businessObject;
@Autowired private MailService mailService;
@Test
@DirtiesContext
public void testBusinessMethod() {
Mockito.when(mailService.sendMail(Mockito.anyObject())).thenReturn(Boolean.TRUE);
businessObject.setMailService(mailService);
assertTrue(businessObject.doWork());
Mockito.verify(mailService).sendMail(Mockito.anyObject());
}
@Test
@DirtiesContext
public void testBusinessMethodMailFail() {
Mockito.when(mailService.sendMail(Mockito.anyObject())).thenThrow(new MailSendException("test"));
businessObject.setMailService(mailService);
assertFalse(businessObject.doWork());
}
}
Jak jsem psal už zkraje – není to nic zázračného, ale použití je skutečně velmi elegantní. Nechápu, že mi něco podobného nedoteklo daleko dřív. Alternativně by se dala použít ještě ProxyFactoryBean, jenže ta je spíš určena na hrátky s AOP než jako podpora testů. Mockito (respektive libovolná jiná mock knihovna) se pro tyto účely hodí výrazně lépe.



K unit testingu a mockum jsem v posledni dobe narazil na dva dobre pluginy do eclipse
1. eclemma pro test coverage http://www.eclemma.org/installation.html
2. MoreUnit ulehcuje generovani testu a nabizi i generovani skeletonu pro Mockito a EasyMock http://moreunit.sourceforge.net/
Myslim zo do kodu by bylo dobre doplnit co dela metoda mock pouzivat v getObject.
Predpokladam ze metoda je staticky importovana z org.mockito.Mockito…
public Object getObject() throws Exception {
return mock(mockClass);
}
Ano je to tak … kvůli přehlednosti jsem odebral importy a tím jsem to naopak znesrozumitelnil. Omlouvám se, situaci napravím.
Zpětný odkaz: » Pátý rok Myšlenek Otce Fura Myšlenky dne otce Fura
Jsem zvyklý na EasyMock a ve srovnání s ním mi Mockito přijde nešikovné v tom, že se opakuje stejný kód v klauzuli when a verify.
Třeba
Mockito.when(mailService.sendMail(Mockito.anyObject())).thenReturn(Boolean.TRUE);
...
Mockito.verify(mailService).sendMail(Mockito.anyObject());
bych napsal jako
EasyMock.expect(mailService.sendMail(EasyMock.anyObject())).andReturn(Boolean.TRUE);
...
EasyMock.verify(mailService);
Nebo jsem něco pochopil špatně?
Pokud by tě nezajímalo, co sendMail vrací, tak by stačilo pouze:
import static Mockito.*;
verify(mailService, times(1)).sendMail(anyObject());
Mockito provádí automatickou capture všech volání aniž by bylo potřeba nějak instruovat. Co je docela hezké je toto:
import static Mockito.*;
ArgumentCaptor
verify(mailService, atLeastOnce()).sendMail(argument.capture());
assertEquals(5, argument.getAllValues().size());
assertEquals("John", argument.getLastValue().getName());
Ale zase jsem už dlouho nepoužil EasyMock, takže nemůžu srovnávat. Mockito mi přišlo na použití hodně příjemné. Koneckonců autoři Mockita přímo na HP píšou, že hlavní inspirací byl pro ně EasyMock a že v podstatě staví na základech, které položil on.
Zpětný odkaz: » Springockito – výroba mocků snadno a rychle Myšlenky dne otce Fura