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.


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.


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?


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.


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


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

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


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.