mock example dzone java unit-testing mockito stubbing

java - example - Mockito cuando/entonces no devuelve el valor esperado



mockito verify (1)

Estoy intentando resguardar este método getKeyFromStream, usando los ''cualesquiera'' matchers. Intenté ser más explícito y menos explícito (anyObject ()), pero parece que no importa lo que intente, este stub no devolverá la clave foo en mi prueba unitaria.

Me pregunto si es porque está protegido o hay algo más que me falta o que estoy haciendo incorrectamente. Tengo otras declaraciones when / then en todas las pruebas que están funcionando pero por alguna razón aquí, no es así.

Nota: El getKeyFromStream generalmente usa un byteArrayInputStream, pero estoy tratando de hacer coincidirlo con un InputStream, he intentado ambos sin éxito.

public class FooKeyRetriever() //Mocked this guy { public FooKey getKey(String keyName) throws KeyException { return getKeyFromStream(getKeyStream(keyName, false), keyName); } //Stubbed this method to return a key object which has been mocked protected FooKey getKeyFromStream(InputStream keyStream, String keyName){ //Some code return fooKey; } }

Prueba de unidad

@Mock private FooKeyRetriever mockKeyRetriever; @Mock private FooKey fooKey; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); } @Test public void testGetFooKey() throws Exception { when(foo.getKeyFromStream(any(InputStream.class),any(String.class))).thenReturn(fooKey); FooKey fooKey = mockKeyRetriever.getKey("irrelevant_key"); assertNotNull(fooKey); }


El problema con su prueba de unidad es que está intentando burlarse de un método de su clase real que desea probar, pero no puede invocar un método de prueba porque devolverá nulo a menos que declare un valor de devolución falso en ese método invocado. Por lo general, solo se burla de las dependencias externas.

En realidad, hay dos maneras de crear objetos de prueba: mock y spy . La primera creará un nuevo objeto basado en la clase que proporcionó, que tiene un estado interno nulo y también devuelve null en cada método invocado. Es por eso que necesita definir ciertos valores de retorno para las invocaciones de métodos. spy por otro lado, crea un objeto real e intercepta las invocaciones de métodos si se definen "definiciones falsas" para ciertos métodos.

Mockito y PowerMock proporcionan dos formas de definir sus métodos de burla:

// method 1 when(mockedObject.methodToMock(any(Param1.class), any(Param2.class),...) .thenReturn(answer); when(mockedObject, method(Dependency.class, "methodToMock", Parameter1.class, Parameter2.class, ...) .thenReturn(answer);

o

// method 2 doReturn(answer).when(mockedObject).methodToMock(param1, param2);

La diferencia es que el method 1 ejecutará la implementación de los métodos, mientras que el último no lo hará. Esto es importante si se trata de objetos spy , ya que a veces no desea ejecutar el código real dentro del método invocado, sino que simplemente reemplaza el código o devuelve un valor predefinido.

Aunque Mockito y PowerMock proporcionan un doCallRealMethod() que puede definir en lugar de doReturn(...) o doThrow(...) , esto invocará y ejecutará el código dentro de su objeto real e ignorará cualquier declaración de retorno de método burlada. Sin embargo, esto no es tan útil en su caso en el que desea burlarse de un método de su clase bajo prueba.

Una implementación de método puede ser "sobrescrita" por

doAnswer(Answer<T>() { @Override public T answer(InvocationOnMock invocation) throws Throwable { ... } )

donde simplemente puede declarar cuál debería ser la lógica del método invocado. Puede utilizar esto para devolver el resultado simulado del método protegido, por lo tanto, así:

import static org.hamcrest.core.IsSame.sameInstance; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doAnswer; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import java.io.InputStream; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; public class FooKeyRetrieverTest { @Test public void testGetFooKey() throws Exception { // Arrange final FooKeyRetriever sut = spy(new FooKeyRetriever()); FooKey mockedKey = mock(FooKey.class); doReturn(mockedKey) .when(sut).getKeyFromStream(any(InputStream.class), anyString()); doAnswer(new Answer<FooKey>() { public FooKey answer(InvocationOnMock invocation) throws Throwable { return sut.getKeyFromStream(null, ""); } }).when(sut).getKey(anyString()); // Act FooKey ret = sut.getKey("test"); // Assert assertThat(ret, sameInstance(mockedKey)); } }

El código anterior funciona, sin embargo, tenga en cuenta que esto tiene la misma semántica que simplemente declarar un valor de retorno para el getKey(...) como

doReturn(mockedKey).when(sut).getKey(anyString());

Intentando modificar solo getKeyFromStream(...) con algo como esto:

doReturn(mockedKey) .when(sut).getKeyFromStream(any(InputStream.class), anyString());

sin modificar getKey(...) de su sistema bajo prueba (SUT) no logrará nada ya que se ejecutará el código real de getKey(...) . Sin embargo, si se burla del sut-object, no podría invocar el método en su sección // Act ya que esto devolvería null. Si intentas

doCallRealMethod().when(sut).getKey(anyString());

en un objeto simulado, el método real sería llamado y como se mencionó previamente, esto también invocaría las implementaciones reales de getKeyFromStream(...) y getKeyStream(...) independientemente de lo que haya especificado como método simulado.

Como probablemente puedas ver por ti mismo, los métodos burlones de tu clase real bajo prueba no son tan útiles y te cargan más de lo que proporcionan ningún bien. Por lo tanto, depende de usted o de la política de su empresa si desea o necesita probar métodos privados / protegidos o si se limita a probar solo la API pública (que recomendaría). También tiene la posibilidad de refactorizar su código para mejorar la capacidad de prueba, aunque la intención principal de la refactorización debería ser mejorar el diseño general de su código .