Junit 5 con Spring Boot: ¿Cuándo usar @ExtendWith Spring o Mockito?

4 minutos de lectura

tengo lo siguiente clase de prueba de unidad abstracta que todas mis clases de pruebas unitarias concretas se extienden:

@ExtendWith(SpringExtension.class)
//@ExtendWith(MockitoExtension.class)
@SpringBootTest(
    classes = PokerApplication.class,
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
public abstract class AbstractUnitTests {

    @MockBean
    public RoundService roundService;

    @MockBean
    public RoundRepository roundRepository;
}

¿Cuál es la diferencia entre usar @ExtendWith(SpringExtension.class) o @ExtendWith(MockitoExtension.class)?

Pregunto porque usar cualquiera de las anotaciones parece no hacer ninguna diferencia y ambas funcionan respectivamente en mi código, lo que me permite usar Junit5. Entonces, ¿por qué ambos funcionan?

Clase de ensayo de hormigón:

    @DisplayName("Test RoundService")
    public class RoundsServiceTest extends AbstractUnitTests {

        private static String STUB_USER_ID = "user3";

        // class under test
        @InjectMocks
        RoundService roundService;

        private Round round;

        private ObjectId objectId;

        @BeforeEach //note this replaces the junit 4 @Before
        public void setUp() {

            initMocks(this);
            round = Mocks.round();
            objectId = Mocks.objectId();
        }

        @DisplayName("Test RoundService.getAllRoundsByUserId()")
        @Test
        public void shouldGetRoundsByUserId() {

            // setup
            given(roundRepository.findByUserId(anyString())).willReturn(Collections.singletonList(round));

            // call method under test
            List<Round> rounds = roundService.getRoundsByUserId(STUB_USER_ID);

            // asserts
            assertNotNull(rounds);
            assertEquals(1, rounds.size());
            assertEquals("user3", rounds.get(0).userId());
        }
}

Sección relevante de Build.gradle :

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.2.2.RELEASE'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    implementation 'junit:junit:4.12'
}

test {
    useJUnitPlatform()
}

Avatar de usuario de RG
RG

¿Qué es una Extensión Junit?

El propósito de las extensiones de Junit 5 es extender el comportamiento de las clases o métodos de prueba.

fuente

Sigue leyendo Modelo de extensión Junit 5 & @ExtendWith anotación:aquí

SpringExtension

SpringExtension integra Spring TestContext Framework en el modelo de programación Júpiter de JUnit 5.

public class SpringExtension
extends Object
implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor, BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback, ParameterResolver{..}

MockitoExtension

Esta extensión es el equivalente JUnit Júpiter de nuestro JUnit4 MockitoJUnitRunner

public class MockitoExtension
extends java.lang.Object
implements BeforeEachCallback, AfterEachCallback, ParameterResolver{..}

Como se puede ver, SpringExtension implementa muchas más extensiones que MockitoExtension.

También @SpringBootTest está meta anotado con @ExtendWith(SpringExtension.class) y lo que significa que cada vez que sus pruebas se amplían con SpringExtension.
@MockBean es una anotación del marco de prueba de Spring y se usa junto con @ExtendWith(SpringExtension.class)

Para observar la diferencia prueba lo siguiente

ExtendWith solo MockitoExtension

@ExtendWith(MockitoExtension.class)
class TestServiceTest {

    @MockBean
    TestService service;

    @Test
    void test() {
        assertNotNull(service); // Test will fail
    }

}

ExtendWith solo SpringExtension

@ExtendWith(SpringExtension.class)
class TestServiceTest {

    @MockBean
    TestService service;

    @Test
    void test() {
        assertNotNull(service); // Test succeeds
    }

}

ExtendWith con ambos SpringExtension y MockitoExtension

@ExtendWith(MockitoExtension.class)
@ExtendWith(SpringExtension.class)
class TestServiceTest {

    @MockBean
    TestService service;

    @Test
    void test() {
        assertNotNull(service); // Test succeeds
    }

}

Ambos funcionan en su caso debido a la @SpringBootTest anotación para la clase de prueba como se explica.

Para responder a la pregunta: Cuándo usar @ExtendWith ¿Primavera o Mockito? ,

Cuando la prueba requiere un contexto de prueba de primavera (para autoconectar un bean/uso de @MockBean ) junto con el uso del modelo de programación Júpiter de JUnit 5 @ExtendWith(SpringExtension.class). Esto también admitirá las anotaciones de Mockito a través de TestExecutionListeners.

Cuando la prueba usa Mockito y necesita el uso de soporte del modelo de programación Júpiter de JUnit 5 @ExtendWith(MockitoExtension.class)

Espero que esto ayude

  • ¿Debería el uso de @InjectMocks trabajar en este contexto con Junit5? Lo intenté y obtuve un error extraño.

    – djangofan

    2 de marzo de 2021 a las 17:58

  • @djangofan publica una pregunta SO con un caso de prueba reproducible

    – RG

    3 de marzo de 2021 a las 2:36

  • Cuándo usar @ExtendWith(SpringExtension.class) o @SpringBootTest?

    – LunaticJape

    23 de julio de 2021 a las 19:26

  • ¿Podemos usar una maqueta en @ExtendWith(SpringExtension.class)?

    – NIRAJ KUMAR

    20 de enero de 2022 a las 9:33

  • Alguna vez lees algo y piensas hombre, desearía que escribieran los documentos así.

    – nerdista

    14 de febrero de 2022 a las 13:59

Cuando usar @ExtendWith(SpringExtension.class) o @SpringBootTest?

  • Cuando usa la prueba de integración -anotación @SpringBootTest- o cualquier prueba de división -anotaciones @xxxTest- no necesita la anotación @ExtendWith(SpringExtension.class) ya que las anotaciones mencionadas la incluyen.

  • Si prueba @ConfigurationProperties, @Service, @Component clase anotada (no definida en los casos de prueba de segmento – ref: Spring Boot Reference Document Testing/Auto-configure/SLICED Tests item-, puede usar @ExtendWith(SpringExtension.class) en lugar de @SpringBootTest.

Observación: Espero que una prueba con @ExtendWith(SpringExtension.class) sea más rápida que la misma prueba con @SpringBootTest. Cuando realizo una prueba en Eclipse observé lo contrario.

  • En nuestro caso, usamos @SpringBootTest y pasamos las clases de configuración específicas necesarias para la prueba. Si, en cambio, usamos solo @ExtendWith(SpringExtension.class), construye todo el contexto de la aplicación, lo que por supuesto lleva más tiempo.

    – Kyrstellaine

    15 de diciembre de 2021 a las 22:07

Avatar de usuario de Excalibur
Excálibur

Para agregar información adicional: también descubrí recientemente que si usa @Mock dependencias anotadas en la clase de prueba con MockitoExtension y tratas de usar Mockito.when(mockedDependency.methodName()) en @BeforeAll método de configuración, entonces obtendrá un NullPointer en tu dependencia burlada.

pero si cambias MockitoExtension a SpringExtension, funciona bien. parece con SpringExtension los frijoles simulados se inicializan antes (antes JUnit lanzamientos @BeforeAll método) tal como debería funcionar correctamente.

¿Ha sido útil esta solución?