Inicialización de objetos simulados - MockIto

122

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

1.

 public class SampleBaseTestCase {

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

2.

@RunWith(MockitoJUnitRunner.class)

[EDITAR] 3.

mock(XXX.class);

sugiéreme si hay otras formas mejores que estas ...

VinayVeluri
fuente

Respuestas:

153

Para la inicialización de los simulacros , usar el corredor o MockitoAnnotations.initMocksson 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 el MockitoAnnotations.initMocks) podría usarse cuando ya haya configurado un corredor específico ( SpringJUnit4ClassRunnerpor ejemplo) en su caso de prueba.

La segunda solución (con la MockitoJUnitRunner) es la más clásica y mi favorita. El código es más sencillo. 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 burlón 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));
    }
}

Ventajas: el código es mínimo

Contras: magia negra. En mi opinión, se debe principalmente a la anotación @InjectMocks. Con esta anotación "pierdes el dolor del código" (mira los grandes comentarios de @Brice )


La tercera solución es crear su simulacro de cada método de prueba. Permite, como explica @mlk en su respuesta, tener una " prueba autónoma ".

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));
    }
}

Ventajas: demuestra claramente cómo funciona su api (BDD ...)

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


Mi recomendación es un compromiso. Utilice la @Mockanotación con @RunWith(MockitoJUnitRunner.class), pero no utilice @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));
    }
}

Ventajas: demuestra claramente cómo funciona su api (cómo ArticleManagerse crea una instancia de my ). Sin código repetitivo.

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

gontard
fuente
Sin embargo, tenga cuidado, las anotaciones son útiles pero no lo protegen contra la creación de un diseño OO deficiente (o para degradarlo). Personalmente, aunque estoy feliz de reducir el código repetitivo, pierdo el dolor del código (o PITA) que es el detonante para cambiar el diseño a uno mejor, por lo que yo y el equipo estamos prestando atención al diseño de OO. Siento que seguir el diseño de OO con principios como el diseño SOLID o las ideas de GOOS es mucho más importante que elegir cómo instanciar simulacros.
Brice
1
(seguimiento) Si no ve cómo se crea este objeto, no siente dolor al respecto y es posible que los programadores futuros no reaccionen bien si se deben agregar nuevas funciones. De todos modos, eso es discutible en ambos sentidos, solo digo que tenga cuidado con eso.
Brice
6
NO ES CORRECTO que estos dos sean equivalentes. NO ES CIERTO que un código más simple sea la única ventaja de usar MockitoJUnitRunner. Para obtener más información sobre las diferencias, consulte la pregunta en stackoverflow.com/questions/10806345/… y mi respuesta.
Dawood ibn Kareem
2
@Gontard Sí, seguro que las dependencias son visibles, pero he visto que el código salió mal con este enfoque. Sobre el uso de Collaborator collab = mock(Collaborator.class), en mi opinión, esta forma es sin duda un enfoque válido. Si bien esto puede tender a ser detallado, puede ganar en comprensibilidad y refactorabilidad de las pruebas. Ambas formas tienen sus pros y sus contras, todavía no he decidido cuál es mejor. Amyway siempre es posible escribir basura, y probablemente depende del contexto y del codificador.
Brice
1
@mlk estoy totalmente de acuerdo contigo. Mi inglés no es muy bueno y carece de matices. Mi punto era insistir en la palabra UNIDAD.
Gontard
30

Ahora hay (a partir de v1.10.7) una cuarta forma de instanciar simulacros, que está utilizando 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 envolver las declaraciones de prueba que proporciona el Runner . El resultado de esto es que puede extraer métodos @Before, métodos @After e incluso intentar ... atrapar envoltorios en reglas. Incluso puede interactuar con estos desde dentro de su prueba, de la forma en que lo hace ExpectedException .

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

En resumen:

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

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

Jeff Bowman
fuente
3
En Kotlin, la regla se ve así:@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
Cristan
10

Hay una forma sencilla de hacer esto.

  • Si es una prueba unitaria, 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 usarse de esa manera con Spring. Solo muestre que puede inicializar simulacros 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() {
        }
    }
emd
fuente
1
Si MOCK también participa en las pruebas de integración, ¿tendrá sentido?
VinayVeluri
2
en realidad no lo hará, tu derecho. Solo quería mostrar las posibilidades de Mockito. Por ejemplo, si usa RESTFuse, debe usar su corredor para que pueda inicializar simulacros con MockitoAnnotations.initMocks (esto);
emd
8

MockitoAnnotations y el corredor se han discutido bien anteriormente, así que voy a arrojar mi tuppence por los no amados:

XXX mockedXxx = mock(XXX.class);

Utilizo esto porque lo encuentro un poco más descriptivo y prefiero (no la prohibición correcta) que las pruebas unitarias no utilicen variables miembro, ya que me gusta que mis pruebas sean (en la medida de lo posible) autónomas.

Michael Lloyd Lee mlk
fuente
¿Existe alguna otra ventaja sobre el uso de mock (XX.class), excepto que el caso de prueba sea autónomo?
VinayVeluri
No que yo sepa.
Michael Lloyd Lee mlk
3
Menos magia que tener que entender para leer la prueba. Declaras la variable y le das un valor, sin anotaciones, reflejo, etc.
Karu
2

Un pequeño ejemplo para JUnit 5 Jupiter, se eliminó "RunWith", ahora necesita usar las extensiones usando la anotación "@ExtendWith".

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}
fl0w
fuente
0

Las otras respuestas son excelentes y contienen más detalles si las desea o las necesita.
Además de esos, me gustaría agregar un TL; DR:

  1. Prefiero usar
    • @RunWith(MockitoJUnitRunner.class)
  2. Si no puede (porque ya usa un corredor diferente), prefiera usar
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. Similar a (2), pero ya no deberías usar esto:
    • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
  4. Si desea usar un simulacro en solo una de las pruebas y no desea exponerlo a otras pruebas en la misma clase de prueba, use
    • X x = mock(X.class)

(1) y (2) y (3) son mutuamente excluyentes.
(4) se puede utilizar en combinación con los demás.

nexo
fuente