Jestem nowicjuszem w programowaniu, aw szczególności w testach jednostkowych. Myślę, że moje wymagania są dość proste, ale chciałbym poznać opinie innych na ten temat.
Załóżmy, że mam dwie takie klasy -
public class First {
Second second ;
public First(){
second = new Second();
}
public String doSecond(){
return second.doSecond();
}
}
class Second {
public String doSecond(){
return "Do Something";
}
}
Powiedzmy, że piszę test jednostkowy, aby przetestować First.doSecond()
metodę. Jednak przypuśćmy, że chcę Mock Second.doSecond()
klasę w ten sposób. Do tego używam Mockito.
public void testFirst(){
Second sec = mock(Second.class);
when(sec.doSecond()).thenReturn("Stubbed Second");
First first = new First();
assertEquals("Stubbed Second", first.doSecond());
}
Widzę, że kpiny nie przynoszą efektu, a stwierdzenie zawodzi. Czy nie ma sposobu na mockowanie zmiennych składowych klasy, którą chcę przetestować. ?
Nie jest to możliwe, jeśli nie możesz zmienić kodu. Ale lubię wstrzykiwanie zależności i Mockito to obsługuje:
public class First { @Resource Second second; public First() { second = new Second(); } public String doSecond() { return second.doSecond(); } }
Twój test:
@RunWith(MockitoJUnitRunner.class) public class YourTest { @Mock Second second; @InjectMocks First first = new First(); public void testFirst(){ when(second.doSecond()).thenReturn("Stubbed Second"); assertEquals("Stubbed Second", first.doSecond()); } }
To bardzo przyjemne i łatwe.
źródło
Jeśli przyjrzysz się uważnie swojemu kodowi, zobaczysz, że
second
właściwość w twoim teście jest nadal instancjąSecond
, a nie próbą (nie przekazujesz jejfirst
w kodzie).Najprostszym sposobem byłoby utworzenie metody ustawiającej
second
wFirst
klasie i jawne przekazanie jej jako makiety.Lubię to:
public class First { Second second ; public First(){ second = new Second(); } public String doSecond(){ return second.doSecond(); } public void setSecond(Second second) { this.second = second; } } class Second { public String doSecond(){ return "Do Something"; } } .... public void testFirst(){ Second sec = mock(Second.class); when(sec.doSecond()).thenReturn("Stubbed Second"); First first = new First(); first.setSecond(sec) assertEquals("Stubbed Second", first.doSecond()); }
Innym byłoby przekazanie
Second
instancji jakoFirst
parametru konstruktora.Jeśli nie możesz zmodyfikować kodu, myślę, że jedyną opcją byłoby użycie odbicia:
public void testFirst(){ Second sec = mock(Second.class); when(sec.doSecond()).thenReturn("Stubbed Second"); First first = new First(); Field privateField = PrivateObject.class. getDeclaredField("second"); privateField.setAccessible(true); privateField.set(first, sec); assertEquals("Stubbed Second", first.doSecond()); }
Ale prawdopodobnie tak, ponieważ rzadko wykonuje się testy na kodzie, nad którym nie kontrolujesz (chociaż można sobie wyobrazić scenariusz, w którym musisz przetestować zewnętrzną bibliotekę, ponieważ jej autor nie :))
źródło
@Mock
i dodaj adnotację First za pomocą@InjectMocks
i utwórz wystąpienie First w inicjatorze. Mockito automatycznie zrobi najlepiej, aby znaleźć miejsce do wstrzyknięcia drugiej makiety do pierwszej instancji, w tym ustawienie pól prywatnych, które pasują do typu.@Mock
było w okolicach 1.5 (może wcześniej, nie jestem pewien). 1.8.3 wprowadzono,@InjectMocks
a także@Spy
i@Captor
.Jeśli nie możesz zmienić zmiennej składowej, to na odwrót jest użycie powerMockit i call
Second second = mock(Second.class) when(second.doSecond()).thenReturn("Stubbed Second"); whenNew(Second.class).withAnyArguments.thenReturn(second);
Problem w tym, że KAŻDE wywołanie nowej Second zwróci tę samą udaną instancję. Ale w twoim prostym przypadku to zadziała.
źródło
Miałem ten sam problem, w którym prywatna wartość nie została ustawiona, ponieważ Mockito nie wywołuje super konstruktorów. Oto jak potęguję kpiny refleksją.
Najpierw utworzyłem klasę TestUtils, która zawiera wiele pomocnych narzędzi, w tym metody odbicia. Dostęp do refleksji jest za każdym razem nieco trudny do wdrożenia. Stworzyłem te metody, aby przetestować kod w projektach, które z jakiegoś powodu nie miały pakietu symulującego i nie zostałem zaproszony do włączenia go.
public class TestUtils { // get a static class value public static Object reflectValue(Class<?> classToReflect, String fieldNameValueToFetch) { try { Field reflectField = reflectField(classToReflect, fieldNameValueToFetch); reflectField.setAccessible(true); Object reflectValue = reflectField.get(classToReflect); return reflectValue; } catch (Exception e) { fail("Failed to reflect "+fieldNameValueToFetch); } return null; } // get an instance value public static Object reflectValue(Object objToReflect, String fieldNameValueToFetch) { try { Field reflectField = reflectField(objToReflect.getClass(), fieldNameValueToFetch); Object reflectValue = reflectField.get(objToReflect); return reflectValue; } catch (Exception e) { fail("Failed to reflect "+fieldNameValueToFetch); } return null; } // find a field in the class tree public static Field reflectField(Class<?> classToReflect, String fieldNameValueToFetch) { try { Field reflectField = null; Class<?> classForReflect = classToReflect; do { try { reflectField = classForReflect.getDeclaredField(fieldNameValueToFetch); } catch (NoSuchFieldException e) { classForReflect = classForReflect.getSuperclass(); } } while (reflectField==null || classForReflect==null); reflectField.setAccessible(true); return reflectField; } catch (Exception e) { fail("Failed to reflect "+fieldNameValueToFetch +" from "+ classToReflect); } return null; } // set a value with no setter public static void refectSetValue(Object objToReflect, String fieldNameToSet, Object valueToSet) { try { Field reflectField = reflectField(objToReflect.getClass(), fieldNameToSet); reflectField.set(objToReflect, valueToSet); } catch (Exception e) { fail("Failed to reflectively set "+ fieldNameToSet +"="+ valueToSet); } } }
Następnie mogę przetestować klasę za pomocą takiej zmiennej prywatnej. Jest to przydatne do wyszydzania głęboko w drzewach klas, nad którymi również nie masz kontroli.
@Test public void testWithRectiveMock() throws Exception { // mock the base class using Mockito ClassToMock mock = Mockito.mock(ClassToMock.class); TestUtils.refectSetValue(mock, "privateVariable", "newValue"); // and this does not prevent normal mocking Mockito.when(mock.somthingElse()).thenReturn("anotherThing"); // ... then do your asserts }
Zmodyfikowałem mój kod z mojego aktualnego projektu tutaj, na stronie. Mogą wystąpić problemy z kompilacją lub dwa. Myślę, że masz ogólny pomysł. Możesz pobrać kod i użyć go, jeśli uznasz to za przydatne.
źródło
Wiele innych osób już radziło ci, aby przemyśleć swój kod, aby był bardziej testowalny - dobra rada i zwykle prostsza niż to, co zamierzam zasugerować.
Jeśli nie możesz zmienić kodu, aby był bardziej testowalny, PowerMock: https://code.google.com/p/powermock/
PowerMock rozszerza Mockito (więc nie musisz uczyć się nowego frameworka), zapewniając dodatkowe funkcje. Obejmuje to możliwość zwrócenia przez konstruktora makiety. Potężny, ale trochę skomplikowany - więc używaj go rozsądnie.
Używasz innego Mock runner. I musisz przygotować klasę, która będzie wywoływała konstruktor. (Zwróć uwagę, że jest to powszechna metoda - przygotuj klasę, która wywołuje konstruktora, a nie skonstruowaną klasę)
@RunWith(PowerMockRunner.class) @PrepareForTest({First.class})
Następnie w konfiguracji testowej możesz użyć metody whenNew, aby konstruktor zwrócił makietę
źródło
Tak, można to zrobić, jak pokazuje poniższy test (napisany za pomocą JMockit mocking API, które tworzę):
@Test public void testFirst(@Mocked final Second sec) { new NonStrictExpectations() {{ sec.doSecond(); result = "Stubbed Second"; }}; First first = new First(); assertEquals("Stubbed Second", first.doSecond()); }
Jednak w przypadku Mockito takiego testu nie da się napisać. Wynika to ze sposobu, w jaki mockowanie jest zaimplementowane w Mockito, gdzie tworzona jest podklasa klasy, która ma być mockowana; tylko instancje tej „pozorowanej” podklasy mogą mieć fałszywe zachowanie, więc testowany kod powinien używać ich zamiast innych instancji.
źródło
Jeśli chcesz mieć alternatywę dla ReflectionTestUtils ze Springa w mockito, użyj
Whitebox.setInternalState(first, "second", sec);
źródło
Możesz mockować dowolną zmienną składową Mockito Mock za pomocą
ReflectionTestUtils
ReflectionTestUtils.setField(yourMock, "memberFieldName", value);
źródło