Teste de método privado usando mockito

104
public class A {

    método public void (boolean b) {
          if (b == true)
               Método 1();
          outro
               método2 ();
    }

    private void method1 () {}
    método void privado2 () {}
}
public class TestA {

    @Teste
    public void testMethod () {
      A a = simulação (classe A.);
      a.method (true);
      // como testar como verify (a) .method1 ();
    }
}

Como testar o método privado é chamado ou não, e como testar o método privado usando mockito ???

Nageswaran
fonte
Apesar de ser específico para o mockito , esta é essencialmente a mesma questão de Como faço para testar uma função privada ou uma classe que possui métodos privados, campos ou classes internas?
Raedwald

Respostas:

81

Você não pode fazer isso com o Mockito, mas pode usar o Powermock para estender o Mockito e simular métodos privados. Powermock suporta Mockito. Aqui está um exemplo.

shift66
fonte
19
Estou confuso com esta resposta. Isso é zombaria, mas o título está testando os métodos privados
diyoda_
Eu usei o Powermock para simular o método privado, mas como posso testar o método privado com o Powermock. Onde posso passar alguma entrada e esperar alguma saída do método e então verificar a saída?
Rito
Você não pode. Você simula a saída de entrada, não pode testar a funcionalidade real.
Talha
131

Não é possível através do mockito. De seu wiki

Por que Mockito não zomba de métodos privados?

Em primeiro lugar, não somos dogmáticos quanto a zombar de métodos privados. Nós simplesmente não nos importamos com métodos privados porque do ponto de vista de teste, métodos privados não existem. Aqui estão alguns motivos pelos quais o Mockito não zomba de métodos privados:

Exige hackeamento de carregadores de classe que nunca é à prova de bala e muda a API (você deve usar o executor de teste personalizado, anotar a classe, etc.).

É muito fácil contornar isso - basta alterar a visibilidade do método de privado para protegido por pacote (ou protegido).

Requer que eu gaste tempo implementando e mantendo-o. E não faz sentido dado o ponto # 2 e o fato de já estar implementado em outra ferramenta (powermock).

Finalmente ... Zombar de métodos privados é uma dica de que há algo errado com o entendimento OO. Em OO, você deseja que objetos (ou funções) colaborem, não métodos. Esqueça o código pascal e procedural. Pense em objetos.

Aravind Yarram
fonte
1
Há uma suposição fatal feita por essa declaração:> Zombar de métodos privados é uma dica de que há algo errado com o entendimento OO. Se estou testando um método público e ele chama métodos privados, gostaria de simular os retornos do método privado. Seguir a suposição acima evita a necessidade de até implementar métodos privados. Como isso é um entendimento pobre de OO?
Eggmatters
1
@eggmatters De acordo com Baeldung "As técnicas de simulação devem ser aplicadas às dependências externas da classe e não à própria classe. Se a simulação de métodos privados é essencial para testar nossas classes, geralmente indica um projeto ruim." Aqui está um tópico legal sobre isso softwareengineering.stackexchange.com/questions/100959/…
Jason Glez
34

Aqui está um pequeno exemplo de como fazer isso com o powermock

public class Hello {
    private Hello obj;
    private Integer method1(Long id) {
        return id + 10;
    }
} 

Para testar o método 1, use o código:

Hello testObj = new Hello();
Integer result = Whitebox.invokeMethod(testObj, "method1", new Long(10L));

Para definir objeto particular obj usar este:

Hello testObj = new Hello();
Hello newObject = new Hello();
Whitebox.setInternalState(testObj, "obj", newObject);
Mindaugas Jaraminas
fonte
Seu link está apontando apenas para o repositório de simulação de energia @Mindaugas
Xavier
@Xavier true. Você pode usá-lo se quiser em seu projeto.
Mindaugas Jaraminas
1
Maravilhoso !!! tão bem explicado com este exemplo simples quase tudo :) Porque o objetivo é apenas testar o código e não o que todo o framework fornece :)
siddhusingh
Por favor, atualize isso. A caixa branca não faz mais parte da API pública.
user447607 01 de
17

Pense nisso em termos de comportamento, não em termos dos métodos que existem. O método chamado methodtem um comportamento particular se bfor verdadeiro. Ele tem um comportamento diferente se bfor falso. Isso significa que você deve escrever dois testes diferentes para method; um para cada caso. Portanto, em vez de ter três testes orientados a método (um para method, um para method1, um para method2, você tem dois testes orientados para o comportamento.

Relacionado a isso (eu sugeri isso em outro tópico do SO recentemente e fui chamado de uma palavra de quatro letras como resultado, então sinta-se à vontade para aceitar isso com um grão de sal); Acho útil escolher nomes de teste que reflitam o comportamento que estou testando, em vez do nome do método. Portanto, não chamar seus testes testMethod(), testMethod1(), testMethod2()e assim por diante. Gosto de nomes como calculatedPriceIsBasePricePlusTax()ou taxIsExcludedWhenExcludeIsTrue()que indicam o comportamento que estou testando; em seguida, em cada método de teste, teste apenas o comportamento indicado. A maioria desses comportamentos envolverá apenas uma chamada para um método público, mas pode envolver muitas chamadas para métodos privados.

Espero que isto ajude.

Dawood ibn Kareem
fonte
13

Embora o Mockito não forneça esse recurso, você pode obter o mesmo resultado usando Mockito + a classe JUnit ReflectionUtils ou a classe Spring ReflectionTestUtils . Veja um exemplo abaixo tirado daqui, explicando como invocar um método privado:

ReflectionTestUtils.invokeMethod(student, "saveOrUpdate", "From Unit test");

Exemplos completos com ReflectionTestUtils e Mockito podem ser encontrados no livro Mockito for Spring

AR1
fonte
ReflectionTestUtils.invokeMethod (aluno, "saveOrUpdate", "argument1", "argument2", "argument3"); O último argumento de invokeMethod, usa Vargs, que pode receber vários argumentos que precisam ser passados ​​para o método privado. funciona.
Tim
Essa resposta deve ter mais votos positivos, de longe a maneira mais fácil de testar métodos privados.
Máx.
9

Você não deve testar métodos privados. Apenas métodos não privados precisam ser testados, pois eles devem chamar os métodos privados de qualquer maneira. Se você "deseja" testar métodos privados, isso pode indicar que você precisa repensar seu design:

Estou usando injeção de dependência adequada? Eu possivelmente preciso mover os métodos privados para uma classe separada e testar isso? Esses métodos devem ser privados? ... eles não podem ser padrão ou protegidos?

No caso acima, os dois métodos chamados "aleatoriamente" podem realmente precisar ser colocados em uma classe própria, testados e, em seguida, injetados na classe acima.

Jaco Van Niekerk
fonte
26
Um ponto válido. No entanto, não é a razão para usar um modificador privado para métodos porque você simplesmente deseja cortar códigos que são muito longos e / ou repetitivos? Separá-lo como outra classe é como se você estivesse promovendo essas linhas de código para serem cidadãos de primeira classe que não serão reutilizados em nenhum outro lugar, porque foi feito especificamente para particionar códigos longos e evitar a repetição de linhas de códigos. Se você vai separá-lo para outra classe, simplesmente não parece certo; você obteria facilmente explosão de classe.
supertonsky
2
Notado supertonsky, eu estava me referindo ao caso geral. Concordo que, no caso acima, não deve ser em uma classe separada. (+1 em seu comentário - é um ponto muito válido que você está fazendo sobre a promoção de membros privados)
Jaco Van Niekerk
4
@supertonsky, não consegui encontrar uma resposta satisfatória para este problema. Existem vários motivos pelos quais eu poderia usar membros privados e, muitas vezes, eles não indicam um cheiro de código e eu me beneficiaria muito testando-os. As pessoas parecem ignorar isso dizendo "simplesmente não faça isso".
LuddyPants
3
Desculpe, optei por votar negativamente com base em "Se você 'quiser' testar métodos privados, isso pode indicar que você precisa votar negativamente em seu design". OK, é justo, mas uma das razões para testar é porque, em um prazo em que você não tem tempo para repensar o design, você está tentando implementar com segurança uma mudança que precisa ser feita em um método privado. Em um mundo ideal, esse método privado não precisaria ser alterado porque o projeto seria perfeito? Claro, mas em um mundo perfeito, mas é discutível porque em um mundo perfeito que precisa de testes, tudo simplesmente funciona. :)
John Lockwood
2
@John. Ponto tomado, seu voto negativo garantido (+1). Obrigado pelo comentário também - concordo com você no que você disse. Nesses casos, posso ver uma das duas opções: ou o método torna-se um pacote privado ou protegido e os testes de unidade são escritos como de costume; ou (e isso é irônico e uma prática ruim) um método principal é escrito rapidamente para garantir que ainda funcione. No entanto, minha resposta foi baseada em um cenário de NOVO código sendo escrito e não refatorado, quando você não conseguirá adulterar o design original.
Jaco Van Niekerk
6

Consegui testar um método privado usando mockito usando reflexão. Aqui está o exemplo, tentei nomeá-lo de forma que faça sentido

//Service containing the mock method is injected with mockObjects

@InjectMocks
private ServiceContainingPrivateMethod serviceContainingPrivateMethod;

//Using reflection to change accessibility of the private method

Class<?>[] params = new Class<?>[]{PrivateMethodParameterOne.class, PrivateMethodParameterTwo.class};
    Method m = serviceContainingPrivateMethod .getClass().getDeclaredMethod("privateMethod", params);
    //making private method accessible
    m.setAccessible(true); 
    assertNotNull(m.invoke(serviceContainingPrivateMethod, privateMethodParameterOne, privateMethodParameterTwo).equals(null));
Abdullah Choudhury
fonte
6
  1. Usando reflexão, métodos privados podem ser chamados de classes de teste. Nesse caso,

    // método de teste será assim ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        // invoke the private method for test
        privateMethod.invoke(A, null);
    
        }
    }
  2. Se o método privado chamar qualquer outro método privado, então precisamos espiar o objeto e fazer o stub do outro método. A classe de teste será como ...

    // método de teste será assim ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        A spyA = spy(a);
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        doReturn("Test").when(spyA, "method2"); // if private method2 is returning string data
        // invoke the private method for test
        privateMethod.invoke(spyA , null);
    
        }
    }

** A abordagem é combinar reflexão e espiar o objeto. ** método1 e ** método2 são métodos privados e método1 chama método2.

Singh Arun
fonte
4

Eu realmente não entendo sua necessidade de testar o método privado. A raiz do problema é que seu método público tem void como tipo de retorno e, portanto, você não pode testar seu método público. Portanto, você é forçado a testar seu método privado. Meu palpite está correto?

Algumas soluções possíveis (AFAIK):

  1. Zombando de seus métodos privados, mas ainda assim você não estará "realmente" testando seus métodos.

  2. Verifique o estado do objeto usado no método. A MAIORIA dos métodos faz algum processamento dos valores de entrada e retorna uma saída, ou altera o estado dos objetos. Testar os objetos para o estado desejado também pode ser empregado.

    public class A{
    
    SomeClass classObj = null;
    
    public void publicMethod(){
       privateMethod();
    }
    
    private void privateMethod(){
         classObj = new SomeClass();
    }
    
    }

    [Aqui você pode testar o método privado, verificando a mudança de estado do classObj de nulo para não nulo.]

  3. Refatore seu código um pouco (espero que este não seja um código legado). Meu fundamento para escrever um método é que sempre se deve retornar algo (um int / um booleano). O valor retornado PODE ou NÃO ser usado pela implementação, mas COM CERTEZA SERÁ usado pelo teste

    código.

    public class A
    { 
        public int method(boolean b)
        {
              int nReturn = 0;
              if (b == true)
                   nReturn = method1();
              else
                   nReturn = method2();
        }
    
        private int method1() {}
    
        private int method2() {}
    
    }
Reji
fonte
3

Na verdade, existe uma maneira de testar métodos de um membro privado com o Mockito. Digamos que você tenha uma aula como esta:

public class A {
    private SomeOtherClass someOtherClass;
    A() {
        someOtherClass = new SomeOtherClass();
    }
    public void method(boolean b){
        if (b == true)
            someOtherClass.method1();
        else
            someOtherClass.method2();
    }

}

public class SomeOtherClass {
    public void method1() {}
    public void method2() {}
}

Se você quiser testar a.methodirá chamar um método de SomeOtherClass, você pode escrever algo como abaixo.

@Test
public void testPrivateMemberMethodCalled() {
    A a = new A();
    SomeOtherClass someOtherClass = Mockito.spy(new SomeOtherClass());
    ReflectionTestUtils.setField( a, "someOtherClass", someOtherClass);
    a.method( true );

    Mockito.verify( someOtherClass, Mockito.times( 1 ) ).method1();
}

ReflectionTestUtils.setField(); irá stubar o membro privado com algo que você pode espionar.

Fan Jin
fonte
2

Coloque seu teste no mesmo pacote, mas em uma pasta de origem diferente (src / main / java vs. src / test / java) e torne esses métodos package-private. A testabilidade do Imo é mais importante do que a privacidade.

Roland Schneider
fonte
6
O único motivo legítimo é testar uma parte de um sistema legado. Se você começar a testar o método private / package-private, você expõe o interior do seu objeto. Fazer isso geralmente resulta em um código refatorável pobre. Prefira a composição para obter testabilidade, com todas as vantagens de um sistema orientado a objetos.
Brice
1
Concordo - essa seria a forma preferida. No entanto, se você realmente deseja testar métodos privados com mockito, esta é a única opção (typesafe) que você tem. Minha resposta foi um pouco precipitada, porém, deveria ter apontado os riscos, como você e os outros fizeram.
Roland Schneider de
Esta é a minha forma preferida. Não há nada de errado em expor o objeto interno no nível privado do pacote; e o teste de unidade é um teste de caixa branca, você precisa saber o interno para teste.
Andrew Feng
0

Nos casos em que o método privado não é nulo e o valor de retorno é usado como parâmetro para um método de dependência externa, você pode simular a dependência e usar um ArgumentCaptorpara capturar o valor de retorno. Por exemplo:

ArgumentCaptor<ByteArrayOutputStream> csvOutputCaptor = ArgumentCaptor.forClass(ByteArrayOutputStream.class);
//Do your thing..
verify(this.awsService).uploadFile(csvOutputCaptor.capture());
....
assertEquals(csvOutputCaptor.getValue().toString(), "blabla");
leitoso
fonte