Zombe de um construtor com parâmetro

90

Eu tenho uma aula como abaixo:

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

    public String check() {
        bla bla bla
    }
}

A lógica no construtor A(String test)e check()são as coisas que estou tentando zombar. Eu quero qualquer chamada como: new A($$$any string$$$).check()retorna uma string fictícia "test".

Eu tentei:

 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

Mas não parece estar funcionando. new A($$$any string$$$).check()ainda está passando pela lógica do construtor em vez de buscar o objeto simulado de A.

Shengjie
fonte
o seu método check () simulado está funcionando bem?
Ben Glasser
@BenGlasser check () funciona bem. Apenas o whenNew não parece funcionar. Eu atualizei a descrição também.
Shengjie

Respostas:

93

O código que você postou funciona para mim com a versão mais recente do Mockito e do Powermockito. Talvez você não tenha preparado A? Experimente isto:

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

Ambos os testes devem passar com mockito 1.9.0, powermockito 1.4.12 e junit 4.8.2

Alban
fonte
25
Observe também que se o construtor for chamado de outra classe, inclua-o na lista emPrepareForTest
Jeff E
Alguém tem uma idéia de por que devemos nos preparar quando "PowerMockito.whenNew" é chamado?
udayanga
50

Pelo que sei, você não pode simular construtores com mockito, apenas métodos. Mas de acordo com o wiki na página de código Mockito do google, há uma maneira de simular o comportamento do construtor criando um método em sua classe que retorna uma nova instância dessa classe. então você pode zombar desse método. Abaixo está um trecho diretamente do wiki Mockito :

Padrão 1 - usando métodos de uma linha para criação de objeto

Para usar o padrão 1 (testando uma classe chamada MyClass), você deve substituir uma chamada como

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

com

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

e escrever um método de uma linha

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

É importante que você não inclua nenhuma lógica no método; apenas uma linha que cria o objeto. A razão para isso é que o método em si nunca será testado em unidades.

Quando você vem para testar a classe, o objeto que você testa será na verdade um espião Mockito, com este método sobrescrito, para retornar um mock. O que você está testando, portanto, não é a classe em si, mas uma versão ligeiramente modificada dela.

Sua classe de teste pode conter membros como

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

Por último, dentro do seu método de teste, você simula a chamada para makeFoo com uma linha como

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

Você pode usar correspondências que são mais específicas do que any () se quiser verificar os argumentos que são passados ​​para o construtor.

Se você está apenas querendo retornar um objeto simulado de sua classe, acho que isso deve funcionar para você. Em qualquer caso, você pode ler mais sobre criação de objeto de simulação aqui:

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

Ben Glasser
fonte
21
+1, não gosto do fato de precisar ajustar meu código-fonte para torná-lo mais amigável para o mockito. Obrigado por compartilhar.
Shengjie
23
Nunca é ruim ter um código-fonte mais testável ou evitar antipadrões de testabilidade ao escrever seu código. Se você escrever uma fonte que seja mais testável, será automaticamente mais fácil de manter. Isolar suas chamadas de construtor em seus próprios métodos é apenas uma maneira de conseguir isso.
Dawood ibn Kareem
1
Escrever código testável é bom. Ser forçado a redesenhar a classe A para que eu possa escrever testes para a classe B, que depende de A, porque A tem uma dependência embutida em C, parece ... menos bom. Sim, o código ficará melhor no final, mas quantas classes vou acabar reprojetando para poder terminar de escrever um teste?
Mark Wood
@MarkWood, em minha experiência, experiências de teste desajeitadas geralmente são um sinal de alguma falha de design. IRL, se você estiver testando construtores, seu código provavelmente está gritando para uma fábrica ou alguma injeção de dependência. Se você seguir os padrões de design típicos para esses dois casos, seu código se tornará muito mais fácil de testar e trabalhar em geral. Se você está testando construtores porque tem muita lógica lá, provavelmente precisa de alguma camada de polimorfismo ou pode mover essa lógica para um método de inicialização.
Ben Glasser
12

Sem usar Powermock .... Veja o exemplo abaixo com base na resposta de Ben Glasser, já que demorei um pouco para descobrir .. espero que economize algum tempo ...

Aula Original:

public class AClazz {

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

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

Classe de Teste

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);
    }
}
usuário666
fonte
7

Com mockito você pode usar withSettings (), por exemplo, se o CounterService requer 2 dependências, você pode passá-las como uma simulação:

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
fonte
Na minha opinião, a resposta mais fácil e melhor. Obrigado.
4

Mockito tem limitações para testar métodos finais, estáticos e privados.

com a biblioteca de testes jMockit, você pode fazer poucas coisas muito fáceis e diretas como a seguir:

Construtor de simulação de uma classe java.io.File:

new MockUp<File>(){
    @Mock
    public void $init(String pathname){
        System.out.println(pathname);
        // or do whatever you want
    }
};
  • o nome do construtor público deve ser substituído por $ init
  • argumentos e exceções lançados permanecem os mesmos
  • tipo de retorno deve ser definido como vazio

Zombe de um método estático:

  • remover estático da assinatura simulada do método
  • a assinatura do método permanece a mesma de outra forma
Amit Kaneria
fonte