Mockito: como verificar se o método foi chamado em um objeto criado dentro de um método?

322

Eu sou novo em Mockito.

Dada a classe abaixo, como posso usar o Mockito para verificar se someMethodfoi chamado exatamente uma vez depois de ter foosido chamado?

public class Foo
{
    public void foo(){
        Bar bar = new Bar();
        bar.someMethod();
    }
}

Gostaria de fazer a seguinte chamada de verificação,

verify(bar, times(1)).someMethod();

Onde baré uma instância zombada de Bar.

senhor
fonte
2
stackoverflow.com/questions/6520242/… - Mas não quero usar o PowerMock.
MRE
Mude a API ou o PowerMock. Um dos dois
John B
Como cobrir algo assim ?? início de void público sincronizado (BundleContext bundleContext) lança Exceção {BundleContext bc = bundleContext; logger.info ("INICIANDO O PACOTE DE SERVIÇO HTTP"); this.tracker = new ServiceTracker (bc, HttpService.class.getName (), null) {@Override public Object addingService (ServiceReference serviceRef) {httpService = (HttpService) super.addingService (serviceRef); registerServlets (); retornar httpService; }}}
ShAkKiR

Respostas:

366

Injeção de dependência

Se você injetar a instância Bar, ou uma fábrica usada para criar a instância Bar (ou uma das outras 483 maneiras de fazer isso), você terá o acesso necessário para realizar o teste.

Exemplo de fábrica:

Dada uma classe Foo escrita assim:

public class Foo {
  private BarFactory barFactory;

  public Foo(BarFactory factory) {
    this.barFactory = factory;
  }

  public void foo() {
    Bar bar = this.barFactory.createBar();
    bar.someMethod();
  }
}

no seu método de teste, você pode injetar um BarFactory como este:

@Test
public void testDoFoo() {
  Bar bar = mock(Bar.class);
  BarFactory myFactory = new BarFactory() {
    public Bar createBar() { return bar;}
  };

  Foo foo = new Foo(myFactory);
  foo.foo();

  verify(bar, times(1)).someMethod();
}

Bônus: Este é um exemplo de como o TDD pode orientar o design do seu código.

csturtz
fonte
6
Existe uma maneira de fazer isso sem modificar a classe para teste de unidade?
MRE
6
Bar bar = mock(Bar.class)em vez deBar bar = new Bar();
John B
7
não que eu saiba. mas não estou sugerindo que você modifique a classe apenas para teste de unidade. Esta é realmente uma conversa sobre código limpo e o SRP. Ou .. é responsabilidade do método foo () na classe Foo construir um objeto Bar. Se a resposta for sim, é um detalhe de implementação e você não deve se preocupar em testar a interação especificamente (consulte a resposta de @ Michael). Se a resposta for não, então você está modificando a classe porque sua dificuldade em testar é uma bandeira vermelha de que seu design precisa de um pouco de melhoria (daí o bônus que eu adicionei sobre como o TDD direciona o design).
22412 csturtz
3
Você pode passar um objeto "real" para a "verificação" de Mockito?
John B
4
Você também pode zombar da fábrica: BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
levsa
18

A resposta clássica é: "Você não". Você testa a API pública de Foo, não suas internas.

Existe algum comportamento do Fooobjeto (ou menos bom, algum outro objeto no ambiente) que é afetado foo()? Se sim, teste isso. E se não, o que o método faz?

Michael Brewer-Davis
fonte
4
Então, o que você realmente testaria aqui? A API pública de Fooé public void foo(), onde os internos são relacionados apenas a barras.
Behelit
15
Testar apenas a API pública é bom, até que haja erros genuínos com efeitos colaterais que precisam de testes. Por exemplo, verificar se um método privado está fechando suas conexões HTTP corretamente é um exagero até você descobrir que o método privado não está fechando suas conexões corretamente e, portanto, está causando um enorme problema. Nesse ponto, Mockito e verify()se torne muito útil, mesmo que você não esteja mais adorando no altar sagrado dos testes de integração.
Dawngerpony
@DuffJ Eu não uso Java, mas isso soa como algo que seu compilador ou ferramenta de análise de código deve detectar.
user247702
3
Eu concordo com o DuffJ, embora a programação funcional seja divertida, chega um momento em que seu código interage com o mundo exterior. Não importa se você o chama de "internos", "efeitos colaterais" ou "funcionalidade", você definitivamente deseja testar essa interação: se isso acontecer e se ocorrer o número correto de vezes e com os argumentos corretos. @ Stijn: pode ter sido um mau exemplo (mas, se várias conexões forem abertas, e apenas algumas fechadas, será interessante). Um exemplo melhor seria verificar o tempo em que os dados corretos teriam sido enviados pela conexão.
Andras Balázs Lajtha
13

Se você não quiser usar DI ou Fábricas. Você pode refatorar sua classe de uma maneira um pouco complicada:

public class Foo {
    private Bar bar;

    public void foo(Bar bar){
        this.bar = (bar != null) ? bar : new Bar();
        bar.someMethod();
        this.bar = null;  // for simulating local scope
    }
}

E sua classe de teste:

@RunWith(MockitoJUnitRunner.class)
public class FooTest {
    @Mock Bar barMock;
    Foo foo;

    @Test
    public void testFoo() {
       foo = new Foo();
       foo.foo(barMock);
       verify(barMock, times(1)).someMethod();
    }
}

Em seguida, a classe que está chamando seu método foo fará o seguinte:

public class thirdClass {

   public void someOtherMethod() {
      Foo myFoo = new Foo();
      myFoo.foo(null);
   }
}

Como você pode ver ao chamar o método dessa maneira, não é necessário importar a classe Bar em nenhuma outra classe que esteja chamando seu método foo, que talvez seja algo que você deseja.

Obviamente, a desvantagem é que você está permitindo que o chamador defina o Objeto de barra.

Espero que ajude.

raspacorp
fonte
3
Eu acho que isso é um anti-padrão. Dependências devem ser injetadas, ponto final. Permitir uma dependência opcionalmente injetada apenas para fins de teste evita intencionalmente o aprimoramento do código e intencionalmente testa algo diferente do código executado na produção. Ambas são coisas horríveis e horríveis de se fazer.
ErikE
8

Solução para o seu código de exemplo usando PowerMockito.whenNew

  • mockito-all 1.10.8
  • powermock-core 1.6.1
  • powermock-module-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • junho 4.12

FooTest.java

package foo;

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

//Both @PrepareForTest and @RunWith are needed for `whenNew` to work 
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Foo.class })
public class FooTest {

    // Class Under Test
    Foo cut;

    @Mock
    Bar barMock;

    @Before
    public void setUp() throws Exception {
        cut = new Foo();

    }

    @After
    public void tearDown() {
        cut = null;

    }

    @Test
    public void testFoo() throws Exception {

        // Setup
        PowerMockito.whenNew(Bar.class).withNoArguments()
                .thenReturn(this.barMock);

        // Test
        cut.foo();

        // Validations
        Mockito.verify(this.barMock, Mockito.times(1)).someMethod();

    }

}

Saída JUnit Saída JUnit

javaPlease42
fonte
8

Eu acho que Mockito @InjectMocksé o caminho a percorrer.

Dependendo da sua intenção, você pode usar:

  1. Injeção de construtor
  2. Injeção de setter de propriedade
  3. Injeção em campo

Mais informações nos documentos

Abaixo está um exemplo com injeção de campo:

Aulas:

public class Foo
{
    private Bar bar = new Bar();

    public void foo() 
    {
        bar.someMethod();
    }
}

public class Bar
{
    public void someMethod()
    {
         //something
    }
}

Teste:

@RunWith(MockitoJUnitRunner.class)
public class FooTest
{
    @Mock
    Bar bar;

    @InjectMocks
    Foo foo;

    @Test
    public void FooTest()
    {
        doNothing().when( bar ).someMethod();
        foo.foo();
        verify(bar, times(1)).someMethod();
    }
}
siulkilulki
fonte
3

Sim, se você realmente deseja / precisa fazer isso, pode usar o PowerMock. Isso deve ser considerado um último recurso. Com o PowerMock, você pode fazer com que ele retorne uma simulação da chamada ao construtor. Em seguida, verifique a simulação. Dito isto, csturtz's é a resposta "certa".

Aqui está o link para a construção simulada de novos objetos

John B
fonte
0

Outra maneira simples seria adicionar alguma instrução de log ao bar.someMethod () e verificar se você pode ver a mensagem mencionada quando o teste foi executado, veja exemplos aqui: Como fazer uma JUnit afirmar uma mensagem em um logger

Isso é especialmente útil quando o seu Bar.someMethod () é private.

Nestor Milyaev
fonte