Inicializando objetos simulados – MockIto

Hay muchas formas de inicializar un objeto simulado utilizando MockIto. ¿Cuál es la mejor manera entre estos?

1.

public class SampleBaseTestCase { @Before public void initMocks() { MockitoAnnotations.initMocks(this); } 

2.

 @RunWith(MockitoJUnitRunner.class) 

[EDITAR] 3.

 mock(XXX.class); 

sugiérame si hay otras formas mejores que estas …

Para la inicialización de los MockitoAnnotations.initMocks , usar el corredor o MockitoAnnotations.initMocks son soluciones estrictamente equivalentes. Desde el javadoc de MockitoJUnitRunner :

JUnit 4.5 runner initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object) is not necessary. Mocks are initialized before each test method.


La primera solución (con MockitoAnnotations.initMocks ) podría usarse cuando ya haya configurado un corredor específico ( SpringJUnit4ClassRunner por ejemplo) en su caso de prueba.

La segunda solución (con MockitoJUnitRunner ) es la más clásica y mi favorita. El código es más simple. El uso de un corredor proporciona la gran ventaja de la validación automática del uso del marco (descrito por @David Wallace en esta respuesta ).

Ambas soluciones permiten compartir los simulacros (y espías) entre los métodos de prueba. Junto con @InjectMocks , permiten escribir pruebas unitarias muy rápidamente. El código de burla repetitivo se reduce, las pruebas son más fáciles de leer. Por ejemplo:

 @RunWith(MockitoJUnitRunner.class) public class ArticleManagerTest { @Mock private ArticleCalculator calculator; @Mock(name = "database") private ArticleDatabase dbMock; @Spy private UserProvider userProvider = new ConsumerUserProvider(); @InjectMocks private ArticleManager manager; @Test public void shouldDoSomething() { manager.initiateArticle(); verify(database).addListener(any(ArticleListener.class)); } @Test public void shouldDoSomethingElse() { manager.finishArticle(); verify(database).removeListener(any(ArticleListener.class)); } } 

Pros: el código es mínimo

Contras: magia negra. IMO se debe principalmente a la anotación @InjectMocks. Con esta anotación “pierdes el dolor del código” (ver los grandes comentarios de @Brice )


La tercera solución es crear tu simulacro en cada método de prueba. Permite que @mlk lo explique en su respuesta para tener ” prueba autocontenida “.

 public class ArticleManagerTest { @Test public void shouldDoSomething() { // given ArticleCalculator calculator = mock(ArticleCalculator.class); ArticleDatabase database = mock(ArticleDatabase.class); UserProvider userProvider = spy(new ConsumerUserProvider()); ArticleManager manager = new ArticleManager(calculator, userProvider, database); // when manager.initiateArticle(); // then verify(database).addListener(any(ArticleListener.class)); } @Test public void shouldDoSomethingElse() { // given ArticleCalculator calculator = mock(ArticleCalculator.class); ArticleDatabase database = mock(ArticleDatabase.class); UserProvider userProvider = spy(new ConsumerUserProvider()); ArticleManager manager = new ArticleManager(calculator, userProvider, database); // when manager.finishArticle(); // then verify(database).removeListener(any(ArticleListener.class)); } } 

Pros: demuestras claramente cómo funciona tu API (BDD …)

Contras: hay más código repetitivo. (La creación de los burla)


Mi recomendación es un compromiso. Use la anotación @Mock con @RunWith(MockitoJUnitRunner.class) , pero no use @InjectMocks :

 @RunWith(MockitoJUnitRunner.class) public class ArticleManagerTest { @Mock private ArticleCalculator calculator; @Mock private ArticleDatabase database; @Spy private UserProvider userProvider = new ConsumerUserProvider(); @Test public void shouldDoSomething() { // given ArticleManager manager = new ArticleManager(calculator, userProvider, database); // when manager.initiateArticle(); // then verify(database).addListener(any(ArticleListener.class)); } @Test public void shouldDoSomethingElse() { // given ArticleManager manager = new ArticleManager(calculator, userProvider, database); // when manager.finishArticle(); // then verify(database).removeListener(any(ArticleListener.class)); } } 

Pros: demuestras claramente cómo funciona tu API (Cómo se instancia mi ArticleManager ). Sin código repetitivo.

Contras: la prueba no es independiente, menos dolor de código

Ahora (a partir de v1.10.7) existe una cuarta forma de crear instancias de simulaciones, que es el uso de una regla JUnit4 llamada MockitoRule .

 @RunWith(JUnit4.class) // or a different runner of your choice public class YourTest @Rule public MockitoRule rule = MockitoJUnit.rule(); @Mock public YourMock yourMock; @Test public void yourTestMethod() { /* ... */ } } 

JUnit busca subclases de TestRule anotadas con @Rule , y las usa para ajustar las declaraciones de prueba que proporciona el Runner . El resultado de esto es que puedes extraer los métodos @Before, @After methods, e incluso intentar … capturar los wrappers en las reglas. Incluso puede interactuar con estos dentro de su prueba, del mismo modo que lo hace ExpectedException .

MockitoRule se comporta casi exactamente como MockitoJUnitRunner , excepto que puedes usar cualquier otro corredor, como Parameterized (que permite a los constructores de prueba tomar argumentos para que tus pruebas puedan ejecutarse varias veces), o el Runner de prueba de Robolectric (para que su cargador de clases pueda proporcionar reemplazos de Java para las clases nativas de Android). Esto lo hace estrictamente más flexible para usar en las versiones recientes de JUnit y Mockito.

En resumen:

  • Mockito.mock() : invocación directa sin soporte de anotación o validación de uso.
  • MockitoAnnotations.initMocks(this) : soporte de anotación, sin validación de uso.
  • MockitoJUnitRunner : soporte de anotación y validación de uso, pero debe usar ese corredor.
  • MockitoRule : compatibilidad con anotaciones y validación de uso con cualquier corredor JUnit.

Ver también: ¿Cómo funciona JUnit @Rule?

Hay una buena manera de hacer esto.

  • Si es una prueba de unidad, puede hacer esto:

     @RunWith(MockitoJUnitRunner.class) public class MyUnitTest { @Mock private MyFirstMock myFirstMock; @Mock private MySecondMock mySecondMock; @Spy private MySpiedClass mySpiedClass = new MySpiedClass(); // It's gonna inject the 2 mocks and the spied object per reflection to this object // The java doc of @InjectMocks explains it really well how and when it does the injection @InjectMocks private MyClassToTest myClassToTest; @Test public void testSomething() { } } 
  • EDITAR: Si se trata de una prueba de integración, puede hacer esto (no está destinado a ser utilizado de esa manera con Spring. Solo muestre que puede inicializar burlas con diferentes Runners):

     @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("aplicationContext.xml") public class MyIntegrationTest { @Mock private MyFirstMock myFirstMock; @Mock private MySecondMock mySecondMock; @Spy private MySpiedClass mySpiedClass = new MySpiedClass(); // It's gonna inject the 2 mocks and the spied object per reflection to this object // The java doc of @InjectMocks explains it really well how and when it does the injection @InjectMocks private MyClassToTest myClassToTest; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); } @Test public void testSomething() { } } 

MockitoAnnotaciones y el corredor se han discutido anteriormente, así que voy a lanzar mi tuppence para los no queridos:

 XXX mockedXxx = mock(XXX.class); 

Utilizo esto porque me parece un poco más descriptivo y prefiero (no a la derecha) pruebas unitarias para no usar variables de miembro ya que me gusta que mis pruebas sean (tanto como puedan) autónomas.