Burlarse de un constructor con parámetro

89

Tengo una clase de la siguiente manera:

public class A {
    public A(String test) {
        bla bla bla
    }

    public String check() {
        bla bla bla
    }
}

La lógica en el constructor A(String test)y check()son las cosas de las que estoy tratando de burlarme. Quiero cualquier llamada como: new A($$$any string$$$).check()devuelve una cadena ficticia "test".

Lo intenté:

 A a = mock(A.class); 
 when(a.check()).thenReturn("test");

 String test = a.check(); // to this point, everything works. test shows as "tests"

 whenNew(A.class).withArguments(Matchers.anyString()).thenReturn(rk);
 // also tried:
 //whenNew(A.class).withParameterTypes(String.class).withArguments(Matchers.anyString()).thenReturn(rk);

 new A("random string").check();  // this doesn't work

Pero no parece estar funcionando. new A($$$any string$$$).check()sigue pasando por la lógica del constructor en lugar de buscar el objeto simulado de A.

Shengjie
fuente
¿Está funcionando correctamente su método check () simulado?
Ben Glasser
@BenGlasser check () funciona bien. Solo el whenNew no parece funcionar en absoluto. También actualicé la descripción.
Shengjie

Respuestas:

93

El código que publicaste funciona para mí con la última versión de Mockito y Powermockito. ¿Quizás no has preparado A? Prueba esto:

A.java

public class A {
     private final String test;

    public A(String test) {
        this.test = test;
    }

    public String check() {
        return "checked " + this.test;
    }
}

MockA.java

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(A.class)
public class MockA {
    @Test
    public void test_not_mocked() throws Throwable {
        assertThat(new A("random string").check(), equalTo("checked random string"));
    }
    @Test
    public void test_mocked() throws Throwable {
         A a = mock(A.class); 
         when(a.check()).thenReturn("test");
         PowerMockito.whenNew(A.class).withArguments(Mockito.anyString()).thenReturn(a);
         assertThat(new A("random string").check(), equalTo("test"));
    }
}

Ambas pruebas deben pasar con mockito 1.9.0, powermockito 1.4.12 y junit 4.8.2

Alban
fuente
24
También tenga en cuenta que si el constructor se llama desde otra clase, inclúyalo en la lista enPrepareForTest
Jeff E
Cualquiera tiene una idea de por qué debemos prepararnos cuando se llama "PowerMockito.whenNew"?
udayanga
50

Que yo sepa, no puedes burlarte de los constructores con mockito, solo métodos. Pero de acuerdo con la wiki en la página de códigos de Google de Mockito, hay una manera de burlarse del comportamiento del constructor creando un método en su clase que devuelva una nueva instancia de esa clase. entonces puedes burlarte de ese método. A continuación se muestra un extracto directamente de la wiki de Mockito :

Patrón 1: uso de métodos de una línea para la creación de objetos

Para usar el patrón 1 (probar una clase llamada MyClass), debe reemplazar una llamada como

   Foo foo = new Foo( a, b, c );

con

   Foo foo = makeFoo( a, b, c );

y escribe un método de una línea

   Foo makeFoo( A a, B b, C c ) { 
        return new Foo( a, b, c );
   }

Es importante que no incluya ninguna lógica en el método; solo la línea que crea el objeto. La razón de esto es que el método en sí mismo nunca será probado por unidad.

Cuando vengas a probar la clase, el objeto que pruebes será en realidad un espía de Mockito, con este método anulado, para devolver una simulación. Por lo tanto, lo que está probando no es la clase en sí, sino una versión ligeramente modificada de ella.

Tu clase de prueba puede contener miembros como

  @Mock private Foo mockFoo;
  private MyClass toTest = spy(new MyClass());

Por último, dentro de su método de prueba, simula la llamada a makeFoo con una línea como

  doReturn( mockFoo )
      .when( toTest )
      .makeFoo( any( A.class ), any( B.class ), any( C.class ));

Puede usar comparadores que sean más específicos que cualquier () si desea verificar los argumentos que se pasan al constructor.

Si solo desea devolver un objeto simulado de su clase, creo que esto debería funcionar para usted. En cualquier caso, puede leer más sobre la creación de objetos simulados aquí:

http://code.google.com/p/mockito/wiki/MockingObjectCreation

Ben Glasser
fuente
21
+1, no me gusta el hecho de que necesito ajustar mi código fuente para hacerlo más compatible con mockito. Gracias por compartir
Shengjie
22
Nunca es malo tener un código fuente que sea más comprobable, o evitar anti-patrones de comprobabilidad cuando escribe su código. Si escribe una fuente que sea más comprobable, automáticamente será más fácil de mantener. Aislar las llamadas de su constructor en sus propios métodos es solo una forma de lograrlo.
Dawood ibn Kareem
1
Escribir código comprobable es bueno. Verme obligado a rediseñar la clase A para poder escribir pruebas para la clase B, que depende de A, porque A tiene una dependencia codificada en C, se siente ... menos bien. Sí, el código será mejor al final, pero ¿cuántas clases terminaré rediseñando para poder terminar de escribir una prueba?
Mark Wood
@MarkWood, en mi experiencia, las experiencias de prueba torpes son generalmente un signo de algún defecto de diseño. IRL, si está probando constructores, su código probablemente le esté gritando por una fábrica o alguna inyección de dependencia. Si sigue patrones de diseño típicos para esos dos casos, su código se vuelve mucho más fácil de probar y trabajar con él en general. Si está probando constructores porque tiene mucha lógica, probablemente necesite alguna capa de polimorfismo, o podría mover esa lógica a un método de inicialización.
Ben Glasser
12

Sin usar Powermock ... Vea el ejemplo a continuación basado en la respuesta de Ben Glasser, ya que me tomó algo de tiempo resolverlo ... espero que ahorre algunas veces ...

Clase original:

public class AClazz {

    public void updateObject(CClazz cClazzObj) {
        log.debug("Bundler set.");
        cClazzObj.setBundler(new BClazz(cClazzObj, 10));
    } 
}

Clase modificada:

@Slf4j
public class AClazz {

    public void updateObject(CClazz cClazzObj) {
        log.debug("Bundler set.");
        cClazzObj.setBundler(getBObject(cClazzObj, 10));
    }

    protected BClazz getBObject(CClazz cClazzObj, int i) {
        return new BClazz(cClazzObj, 10);
    }
 }

Clase de prueba

public class AClazzTest {

    @InjectMocks
    @Spy
    private AClazz aClazzObj;

    @Mock
    private CClazz cClazzObj;

    @Mock
    private BClazz bClassObj;

    @Before
    public void setUp() throws Exception {
        Mockito.doReturn(bClassObj)
               .when(aClazzObj)
               .getBObject(Mockito.eq(cClazzObj), Mockito.anyInt());
    }

    @Test
    public void testConfigStrategy() {
        aClazzObj.updateObject(cClazzObj);

        Mockito.verify(cClazzObj, Mockito.times(1)).setBundler(bClassObj);
    }
}
usuario666
fuente
6

Con mockito puede usar withSettings (), por ejemplo, si CounterService requiere 2 dependencias, puede pasarlas como una simulación:

UserService userService = Mockito.mock(UserService.class); SearchService searchService = Mockito.mock(SearchService.class); CounterService counterService = Mockito.mock(CounterService.class, withSettings().useConstructor(userService, searchService));

MevlütÖzdemir
fuente
En mi opinión, la mejor y más fácil respuesta. Gracias.
Eldon
4

Mockito tiene limitaciones para probar métodos finales, estáticos y privados.

con la biblioteca de pruebas jMockit, puede hacer algunas cosas de manera muy fácil y directa como se muestra a continuación:

Constructor simulado de una clase java.io.File:

new MockUp<File>(){
    @Mock
    public void $init(String pathname){
        System.out.println(pathname);
        // or do whatever you want
    }
};
  • el nombre del constructor público debe reemplazarse por $ init
  • Los argumentos y excepciones arrojados siguen siendo los mismos
  • el tipo de retorno debe definirse como vacío

Simulacros de un método estático:

  • eliminar estática de la firma simulada del método
  • la firma del método permanece igual de lo contrario
Amit Kaneria
fuente