Mocks erzeugen für via Class spezifizierte Objekte
Probleme:
Problem 1: Dynamisches Mocking:
Eine Methode soll Mocks für verschiedene Service-Klassen definieren. In der Methode selbst kann nicht jede in ihr zu Mockende Klasse separat auscodiert werden.
D.h.:
Mockito.when(MeineKlasse.meinMethode())
ist nicht möglich.
Stattdessen wird der Methode ein Class -Objekt übergeben, die den zu mockenden Service definiert.
Problem 2:
Wenn ein bestimmter request vorliegt muss ein definierter Reply zurück gegeben werden.
MyObj2 mock = Mockito.mock(MyObj2.class); Mockito.when(mock.returnOnObj(new Bohne1("X"))).thenReturn("desired return");
Problem 3 :
Definition einer Sequenz der durch den Mock zurückzugebender Return-Werte.
MyObj2 mock = Mockito.mock(MyObj2.class); Mockito.when(mock.returnOnObj(Mockito.any())).thenReturn("1").thenReturn("2").thenReturn("3");
Beispiel, das alles obigen Probleme löst:
HPServiceMockFactory.
import org.mockito.stubbing.OngoingStubbing; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.when; public class HPServiceMockFactory<T> { public enum MockMatchingType {IN_SEQUENCE, BY_REQUEST_MATCH}; public <T> T prepareMock(T mock, Class bdType, String svcName, MockMatchingType mockMatchingType, Object[] parameters, Object[] desiredReturns){ Method svc = null; for (Method method : bdType.getDeclaredMethods()) { // check if method with this name and number of arguments is declared if ((method.getParameterTypes().length == parameters.length) && (method.getName().equals(svcName))) { // check method parameters if (allinstanceof(parameters, method.getParameterTypes())) { try { Object[] paramsForInvocation = new Object[parameters.length]; for(int i = 0; i < parameters.length; i++){ if(mockMatchingType.equals(MockMatchingType.IN_SEQUENCE)){ // When we want to invoke in sequence: First invocation gets desiredReturn[0], second gets desiredReturn[1], ... // Then we should narrow only to anyObject parameters (or we could narrow the the suiting parameter classes) paramsForInvocation[i] = anyObject(); }else{ // When we want the mock to return an answer which is defined by its request being equal as the one when the mock is called // then we have to call the mock and pass the expected parameters BEFORE (farther below) define the return values (via the "thenReturn(..)) paramsForInvocation[i] = parameters[i]; } } svc = bdType.getDeclaredMethod(svcName, method.getParameterTypes()); svc.invoke(mock, paramsForInvocation); } catch (NoSuchMethodException e) { throw new IllegalStateException("Failed to initialize mock data, " + e.toString(), e); } catch (InvocationTargetException e) { throw new IllegalStateException("Failed to initialize mock data, " + e.toString(), e); } catch (IllegalAccessException e) { throw new IllegalStateException("Failed to initialize mock data, " + e.toString(), e); } } } } if (svc == null) { throw new IllegalStateException("Service method could not be found"); } OngoingStubbing<Object> ongoingStubbing = when(svc); for(Object desiredReturn : desiredReturns){ ongoingStubbing = ongoingStubbing.thenReturn(desiredReturn); } return mock; } private boolean allinstanceof(Object obj[], Class<?> classes[]) { for (int i = 0; i < obj.length; i++) { Class<?> clazz = classes[i]; Object o = obj[i]; if (clazz.isPrimitive()) { if (clazz == boolean.class) { if (!(o instanceof Boolean)) { return false; } } else if (clazz == int.class) { if (!(o instanceof Integer)) { return false; } } } else { if (o != null && !clazz.isInstance(o)) { return false; } } } return true; } }
Aufrufende Klasse:
import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import java.util.Objects; import static com.ubs.m0m.mock.HPServiceMockFactory.MockMatchingType.BY_REQUEST_MATCH; import static com.ubs.m0m.mock.HPServiceMockFactory.MockMatchingType.IN_SEQUENCE; /** * Demonstrates how Mockito.when(null) can be used to tell Mockito to * return a given value if objects equals to given ones are passed to the mocked method */ public class HPServiceMockTest { @Test public void mustReturnMockAnswersAsDefinedInASequence(){ HPService mockedObject = Mockito.mock(HPService.class); Object[] parameters = new Object[1]; parameters[0] = new Bohne1("X"); String[] desiredReturns = {"Response 1", "Response 2", "Response 3"}; HPServiceMockFactory myServiceMockFactory = new HPServiceMockFactory<HPService>(); myServiceMockFactory.prepareMock(mockedObject, HPService.class, "returnOnObj", IN_SEQUENCE, parameters, desiredReturns); Assert.assertEquals(desiredReturns[0], mockedObject.returnOnObj(new Bohne1("ANY"))); Assert.assertEquals(desiredReturns[1], mockedObject.returnOnObj(new Bohne1("ANY"))); Assert.assertEquals(desiredReturns[2], mockedObject.returnOnObj(new Bohne1("ANY"))); } @Test public void mustReturnMockAnswersThatMatchTheGivenRequest(){ HPServiceMockFactory myServiceMockFactory = new HPServiceMockFactory<HPService>(); HPService mockedObject = Mockito.mock(HPService.class); Object[] whenReqParams1 = new Object[1]; whenReqParams1[0] = new Bohne1("Request that must match 1"); String[] desiredReturns1 = {"Right Response 1"}; myServiceMockFactory.prepareMock(mockedObject, HPService.class, "returnOnObj", BY_REQUEST_MATCH, whenReqParams1, desiredReturns1); Object[] whenReqParams2 = new Object[1]; whenReqParams2[0] = new Bohne1("Request that must match 2"); String[] desiredReturns2 = {"Right Response 2"}; myServiceMockFactory.prepareMock(mockedObject, HPService.class, "returnOnObj", BY_REQUEST_MATCH, whenReqParams2, desiredReturns2); Assert.assertEquals(desiredReturns1[0], mockedObject.returnOnObj(new Bohne1("Request that must match 1"))); Assert.assertEquals(desiredReturns2[0], mockedObject.returnOnObj(new Bohne1("Request that must match 2"))); Assert.assertNull(mockedObject.returnOnObj(new Bohne1("ANY"))); } } class HPService { public String returnOnObj(Bohne1 in){return "not mocked";} } class Bohne1 { public String id; public Bohne1(String id) { this.id = id; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Bohne1)) return false; Bohne1 bohne1 = (Bohne1) o; return Objects.equals(id, bohne1.id); } @Override public int hashCode() { return Objects.hash(id); } }
Dynamische Mocking
Probleme:
Problem 1: Dynamisches Mocking:
Eine Methode soll Mocks für verschiedene Service-Klassen definieren. In der Methode selbst kann nicht jede in ihr zu Mockende Klasse separat auscodiert werden.
D.h.:
Mockito.when(MeineKlasse.meinMethode())
ist nicht möglich.
Stattdessen wird der Methode ein Class -Objekt übergeben, die den zu mockenden Service definiert.
Problem 2:
Wenn ein bestimmter request vorliegt muss ein definierter Reply zurück gegeben werden.
MyObj2 mock = Mockito.mock(MyObj2.class); Mockito.when(mock.returnOnObj(new Bohne1("X"))).thenReturn("desired return");
Problem 3 :
Definition einer Sequenz der durch den Mock zurückzugebender Return-Werte.
MyObj2 mock = Mockito.mock(MyObj2.class); Mockito.when(mock.returnOnObj(Mockito.any())).thenReturn("1").thenReturn("2").thenReturn("3");
Beispiel, das alles obigen Probleme löst:
HPServiceMockFactory.class
import org.mockito.stubbing.OngoingStubbing; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.when; public class HPServiceMockFactory<T> { public enum MockMatchingType {IN_SEQUENCE, BY_REQUEST_MATCH}; public <T> T prepareMock(T mock, Class bdType, String svcName, MockMatchingType mockMatchingType, Object[] parameters, Object[] desiredReturns){ Method svc = null; for (Method method : bdType.getDeclaredMethods()) { // check if method with this name and number of arguments is declared if ((method.getParameterTypes().length == parameters.length) && (method.getName().equals(svcName))) { // check method parameters if (allinstanceof(parameters, method.getParameterTypes())) { try { Object[] paramsForInvocation = new Object[parameters.length]; for(int i = 0; i < parameters.length; i++){ if(mockMatchingType.equals(MockMatchingType.IN_SEQUENCE)){ // When we want to invoke in sequence: First invocation gets desiredReturn[0], second gets desiredReturn[1], ... // Then we should narrow only to anyObject parameters (or we could narrow the the suiting parameter classes) paramsForInvocation[i] = anyObject(); }else{ // When we want the mock to return an answer which is defined by its request being equal as the one when the mock is called // then we have to call the mock and pass the expected parameters BEFORE (farther below) define the return values (via the "thenReturn(..)) paramsForInvocation[i] = parameters[i]; } } svc = bdType.getDeclaredMethod(svcName, method.getParameterTypes()); svc.invoke(mock, paramsForInvocation); } catch (NoSuchMethodException e) { throw new IllegalStateException("Failed to initialize mock data, " + e.toString(), e); } catch (InvocationTargetException e) { throw new IllegalStateException("Failed to initialize mock data, " + e.toString(), e); } catch (IllegalAccessException e) { throw new IllegalStateException("Failed to initialize mock data, " + e.toString(), e); } } } } if (svc == null) { throw new IllegalStateException("Service method could not be found"); } OngoingStubbing<Object> ongoingStubbing = when(svc); for(Object desiredReturn : desiredReturns){ ongoingStubbing = ongoingStubbing.thenReturn(desiredReturn); } return mock; } private boolean allinstanceof(Object obj[], Class<?> classes[]) { for (int i = 0; i < obj.length; i++) { Class<?> clazz = classes[i]; Object o = obj[i]; if (clazz.isPrimitive()) { if (clazz == boolean.class) { if (!(o instanceof Boolean)) { return false; } } else if (clazz == int.class) { if (!(o instanceof Integer)) { return false; } } } else { if (o != null && !clazz.isInstance(o)) { return false; } } } return true; } }
Test Klassen:
import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import java.util.Objects; import static com.ubs.m0m.mock.HPServiceMockFactory.MockMatchingType.BY_REQUEST_MATCH; import static com.ubs.m0m.mock.HPServiceMockFactory.MockMatchingType.IN_SEQUENCE; /** * Demonstrates how Mockito.when(null) can be used to tell Mockito to * return a given value if objects equals to given ones are passed to the mocked method */ public class HPServiceMockTest { @Test public void mustReturnMockAnswersAsDefinedInASequence(){ HPService mockedObject = Mockito.mock(HPService.class); Object[] parameters = new Object[1]; parameters[0] = new Bohne1("X"); String[] desiredReturns = {"Response 1", "Response 2", "Response 3"}; HPServiceMockFactory myServiceMockFactory = new HPServiceMockFactory<HPService>(); myServiceMockFactory.prepareMock(mockedObject, HPService.class, "returnOnObj", IN_SEQUENCE, parameters, desiredReturns); Assert.assertEquals(desiredReturns[0], mockedObject.returnOnObj(new Bohne1("ANY"))); Assert.assertEquals(desiredReturns[1], mockedObject.returnOnObj(new Bohne1("ANY"))); Assert.assertEquals(desiredReturns[2], mockedObject.returnOnObj(new Bohne1("ANY"))); } @Test public void mustReturnMockAnswersThatMatchTheGivenRequest(){ HPServiceMockFactory myServiceMockFactory = new HPServiceMockFactory<HPService>(); HPService mockedObject = Mockito.mock(HPService.class); Object[] whenReqParams1 = new Object[1]; whenReqParams1[0] = new Bohne1("Request that must match 1"); String[] desiredReturns1 = {"Right Response 1"}; myServiceMockFactory.prepareMock(mockedObject, HPService.class, "returnOnObj", BY_REQUEST_MATCH, whenReqParams1, desiredReturns1); Object[] whenReqParams2 = new Object[1]; whenReqParams2[0] = new Bohne1("Request that must match 2"); String[] desiredReturns2 = {"Right Response 2"}; myServiceMockFactory.prepareMock(mockedObject, HPService.class, "returnOnObj", BY_REQUEST_MATCH, whenReqParams2, desiredReturns2); Assert.assertEquals(desiredReturns1[0], mockedObject.returnOnObj(new Bohne1("Request that must match 1"))); Assert.assertEquals(desiredReturns2[0], mockedObject.returnOnObj(new Bohne1("Request that must match 2"))); Assert.assertNull(mockedObject.returnOnObj(new Bohne1("ANY"))); } } class HPService { public String returnOnObj(Bohne1 in){return "not mocked";} } class Bohne1 { public String id; public Bohne1(String id) { this.id = id; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Bohne1)) return false; Bohne1 bohne1 = (Bohne1) o; return Objects.equals(id, bohne1.id); } @Override public int hashCode() { return Objects.hash(id); } }
... code ...
Mockito.when(null) – Return basierend auf equality zu gegebenem Input
Mockito.mock(null)
Weshalb sollte das sinn machen?
Mockito.when(null).thenReturn(meinObjekt)
bedeutet eigentlich: Liebes Mockito. Nimm den letzten Aufruf auf irgend einem Mock. Wenn irgendwann derselbe Aufruf mit denselben Parametern gemacht werden sollte, dann gib „meinObjekt“ zurück.
Dies wird unten demonstriert.
import org.mockito.Mockito; import java.util.Objects; /** * Demonstrates how Mockito.when(null) can be used to tell Mockito to * return a given value if objects equals to given ones are passed to the mocked method */ public class HPTest { public static void main(String[] args) { MyObj mockedObject = Mockito.mock(MyObj.class); /* Tell Mockito, that if get1() is called, then return "super cool string 1.1"*/ mockedObject.get1(); Mockito.when(null).thenReturn("super cool string 1.1"); mockedObject.get2(); Mockito.when(null).thenReturn("super cool string 2"); System.out.println(mockedObject.get1()); System.out.println(mockedObject.get1()); System.out.println(mockedObject.get2()); System.out.println(mockedObject.toString()); /* Tell Mockito, that if return1("1") is called, then return "Return 1" */ mockedObject.return1("1"); Mockito.when(null).thenReturn("Return 1"); /* but if return1("2") is called, then return "Return 2" */ mockedObject.return1("2"); Mockito.when(null).thenReturn("Return 2"); System.out.println(mockedObject.return1("1")); System.out.println(mockedObject.return1("2")); System.out.println(mockedObject.return1("2")); System.out.println(mockedObject.return1("1")); mockedObject.returnOnObj(new Bohne("A")); Mockito.when(null).thenReturn("On Obj A"); mockedObject.returnOnObj(new Bohne("B")); Mockito.when(null).thenReturn("On Obj B"); System.out.println(mockedObject.returnOnObj(new Bohne("A"))); System.out.println(mockedObject.returnOnObj(new Bohne("B"))); System.out.println(mockedObject.returnOnObj(new Bohne("B"))); System.out.println(mockedObject.returnOnObj(new Bohne("A"))); } } class MyObj { public String get1(){return "not mocked 1";} public String get2(){return "not mocked 2";} public String return1(String in){return "not mocked";} public String returnOnObj(Bohne in){return "not mocked";} } class Bohne { public String id; public Bohne(String id) { this.id = id; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Bohne)) return false; Bohne bohne = (Bohne) o; return Objects.equals(id, bohne.id); } @Override public int hashCode() { return Objects.hash(id); } } /* Output: super cool string 1.1 super cool string 1.1 super cool string 2 Mock for MyObj, hashCode: 650023597 Return 1 Return 2 Return 2 Return 1 On Obj A On Obj B On Obj B On Obj A Process finished with exit code 0 */