Usando Mockito para simular clases con parámetros genéricos

6 minutos de lectura

¿Existe un método limpio para burlarse de una clase con parámetros genéricos? Di que tengo que burlarme de una clase Foo<T> que necesito pasar a un método que espera un Foo<Bar>. Puedo hacer lo siguiente con bastante facilidad:

Foo mockFoo = mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());

Asumiendo getValue() devuelve el tipo genérico T. Pero eso va a tener gatitos cuando luego lo pase a un método esperando Foo<Bar>. ¿Es el casting el único medio de hacer esto?

  • ¿Por qué usar foo y bar sobre nombres más significativos? Acabo de crear una gran confusión para mucha gente.

    – Kaigo

    25 de julio de 2020 a las 20:26

  • @Kaigo es bastante típico en los ejemplos de programación usar foo, bar y baz, especialmente cuando se anonimizan ejemplos del mundo real para ocultar detalles confidenciales. ahora, estoy más preocupado por el hecho de que OP usó la frase “eso va a tener gatitos”… literalmente, nunca escuché a nadie decir eso antes;)

    – Adam Burley

    13 de marzo de 2021 a las 14:04

  • Todo el mundo sabe que “tener gatitos” es algo malo para el código, incluso si es bastante bueno para los gatos. :--)

    –Charlie Reitzel

    29 de enero a las 18:12

avatar de usuario
Juan Paulett

Creo que necesitas lanzarlo, pero no debería ser tan malo:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());

  • Sí, pero todavía tienes una advertencia. ¿Es posible evitar la advertencia?

    – búho

    31 de agosto de 2010 a las 19:11

  • @SuppressWarnings(“desmarcado”)

    – calificativo

    10 de diciembre de 2010 a las 1:13

  • Creo que esto es totalmente aceptable ya que estamos hablando de un objeto simulado en una prueba unitaria.

    – Magnilex

    26 de noviembre de 2014 a las 13:06

  • @demaniak No funciona en absoluto. Los comparadores de argumentos no se pueden usar en ese contexto.

    – Krzysztof Krason

    19 mayo 2018 a las 20:53


  • @demaniak Eso se compilará bien, pero al ejecutar la prueba arrojará InvalidUseOfMatchersException (que es una RuntimeException)

    – Superol

    4 de junio de 2018 a las 12:32

avatar de usuario
Marek Kirejczyk

Otra forma de evitar esto es usar @Mock anotación en su lugar. No funciona en todos los casos, pero se ve mucho más sexy 🙂

Aquí hay un ejemplo:

@RunWith(MockitoJUnitRunner.class)
public class FooTests {

    @Mock
    public Foo<Bar> fooMock;
    
    @Test
    public void testFoo() {
        when(fooMock.getValue()).thenReturn(new Bar());
    }
}

los MockitoJUnitRunner inicializa los campos anotados con @Mock.

  • esto está en desuso en 1.9.5. 🙁 Me parece mucho más limpio.

    – Función pura

    27/03/2014 a las 21:36

  • @CodeNovitiate No pude encontrar ninguna anotación de desaprobación en MockitoJUnitRunner y Mock en 1.9.5. Entonces, ¿qué está en desuso? (Sí, org.mockito.MockitoAnnotations.Mock está en desuso, pero debería usar org.mockito.Mock en su lugar)

    – neu242

    22 de mayo de 2014 a las 7:31

  • Bien hecho, esto funcionó perfectamente para mí. No es solo “más sexy”, evita una advertencia sin usar SuppressWarnings. Las advertencias existen por una razón, es mejor no tener el hábito de suprimirlas. ¡Gracias!

    – Nicole

    3 de junio de 2014 a las 17:43

  • Hay una cosa que no me gusta de usar @Mock en vez de mock(): los campos aún son nulos durante el tiempo de construcción, por lo que no puedo insertar dependencias en ese momento y no puedo hacer que los campos sean definitivos. Lo primero se puede resolver con un @Before-Método anotado por supuesto.

    – Rüdiger Schulz

    25 de septiembre de 2014 a las 9:26


  • Para la iniciación, simplemente llame a MockitoAnnotations.initMocks(this);

    – borjab

    06/04/2016 a las 12:31

avatar de usuario
dsingleton

Siempre puede crear una clase/interfaz intermedia que satisfaga el tipo genérico que desea especificar. Por ejemplo, si Foo fuera una interfaz, podría crear la siguiente interfaz en su clase de prueba.

private interface FooBar extends Foo<Bar>
{
}

En situaciones donde Foo es un no final clase, podría extender la clase con el siguiente código y hacer lo mismo:

public class FooBar extends Foo<Bar>
{
}

Entonces podría consumir cualquiera de los ejemplos anteriores con el siguiente código:

Foo<Bar> mockFoo = mock(FooBar.class);
when(mockFoo.getValue()).thenReturn(new Bar());

  • Previsto Foo es una interfaz o clase no final, esta parece ser una solución razonablemente elegante. Gracias.

    –Tim Clemons

    29 de abril de 2014 a las 15:39

  • Actualicé la respuesta para incluir ejemplos para clases no finales también. Lo ideal sería que estuviera codificando contra una interfaz, pero ese no siempre será el caso. ¡Buena atrapada!

    – dsingleton

    30 de abril de 2014 a las 15:15

Crear un método de utilidad de prueba. Especialmente útil si lo necesitas para más de una vez.

@Test
public void testMyTest() {
    // ...
    Foo<Bar> mockFooBar = mockFoo();
    when(mockFooBar.getValue).thenReturn(new Bar());

    Foo<Baz> mockFooBaz = mockFoo();
    when(mockFooBaz.getValue).thenReturn(new Baz());

    Foo<Qux> mockFooQux = mockFoo();
    when(mockFooQux.getValue).thenReturn(new Qux());
    // ...
}

@SuppressWarnings("unchecked") // still needed :( but just once :)
private <T> Foo<T> mockFoo() {
    return mock(Foo.class);
}

Estoy de acuerdo en que uno no debe suprimir las advertencias en clases o métodos, ya que podría pasar por alto otras advertencias suprimidas accidentalmente. Pero en mi humilde opinión, es absolutamente razonable suprimir una advertencia que afecta solo a una sola línea de código.

@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = mock(Foo.class);

Como mencionaron las otras respuestas, no hay una excelente manera de usar el mock() & spy() métodos directamente sin acceso genérico inseguro y/o supresión de advertencias genéricas.

Actualmente hay un problema abierto en el proyecto Mockito (#1531) para añadir compatibilidad con el uso de mock() & spy() métodos sin advertencias genéricas. El problema se abrió en noviembre de 2018, pero no hay indicios de que se le dé prioridad. Según uno de los comentarios del colaborador de Mockito sobre el tema:

Dado que .class no funciona bien con los genéricos, no creo que haya ninguna solución que podamos hacer en Mockito. ya puedes hacer @Mock (la extensión JUnit5 también permite el parámetro de método @Mocks) y que debe ser una alternativa adecuada. Por lo tanto, podemos mantener este problema abierto, pero es poco probable que se solucione, dado que @Mock es una mejor API.

avatar de usuario
Vogella

Con JUnit5, creo que la mejor manera es @ExtendWith(MockitoExtension.class) con @Mock en el parámetro del método o el campo.

El siguiente ejemplo demuestra eso con los emparejadores Hamcrest.

package com.vogella.junit5;                                                                    
                                                                                               
import static org.hamcrest.MatcherAssert.assertThat;                                           
import static org.hamcrest.Matchers.hasItem;                                                   
import static org.mockito.Mockito.verify;                                                      
                                                                                               
import java.util.Arrays;                                                                       
import java.util.List;                                                                         
                                                                                               
import org.junit.jupiter.api.Test;                                                             
import org.junit.jupiter.api.extension.ExtendWith;                                             
import org.mockito.ArgumentCaptor;                                                             
import org.mockito.Captor;                                                                     
import org.mockito.Mock;                                                                       
import org.mockito.junit.jupiter.MockitoExtension;                                             
                                                                                               
@ExtendWith(MockitoExtension.class)                                                            
public class MockitoArgumentCaptureTest {                                                      
                                                                                               
                                                                                               
    @Captor                                                                                    
    private ArgumentCaptor<List<String>> captor;                                               
                                                                                               
    @Test                                                                                      
    public final void shouldContainCertainListItem(@Mock List<String> mockedList) {            
        var asList = Arrays.asList("someElement_test", "someElement");                         
        mockedList.addAll(asList);                                                             
                                                                                               
        verify(mockedList).addAll(captor.capture());                                           
        List<String> capturedArgument = captor.getValue();                                     
        assertThat(capturedArgument, hasItem("someElement"));                                  
    }                                                                                          
}                                                                                              
                                                                                              

Ver https://www.vogella.com/tutorials/Mockito/article.html para las dependencias requeridas de Maven/Gradle.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad