Como zombar de uma aula final com mockito

218

Eu tenho uma aula final, algo como isto:

public final class RainOnTrees{

   public void startRain(){

        // some code here
   }
}

Estou usando esta classe em alguma outra classe como esta:

public class Seasons{

   RainOnTrees rain = new RainOnTrees();

   public void findSeasonAndRain(){

        rain.startRain();

    }
}

e na minha classe de teste JUnit, Seasons.javaquero zombar da RainOnTreesclasse. Como posso fazer isso com o Mockito?

buttowski
fonte
9
O Mockito não permite, no entanto, o PowerMock.
fge 12/01
1
A partir do Mockito 2.x, o Mockito agora suporta a simulação de classes e métodos finais.
Kent
Possível duplicata da classe final
eliasah

Respostas:

155

A zombaria de classes / métodos estáticos / finais é possível apenas com o Mockito v2.

adicione isso no seu arquivo gradle:

testImplementation 'org.mockito:mockito-inline:2.13.0'

Isso não é possível com o Mockito v1, nas Perguntas frequentes do Mockito :

Quais são as limitações do Mockito

  • Precisa de java 1.5+

  • Não é possível zombar das classes finais

...

akshay bhange
fonte
2
Isso não funcionou para mim no Scala (com modificações sbt).
micseydel
2
Isso não foi suficiente para mim. Também tive que criar src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker com "mock-maker-inline", conforme baeldung.com/mockito-final
micseydel
204

O Mockito 2 agora suporta classes e métodos finais !

Mas, por enquanto, esse é um recurso de "incubação". Requer algumas etapas para ativá-lo, descritas em O que há de novo no Mockito 2 :

A zombaria das classes e métodos finais é um recurso de incubação e aceitação. Ele usa uma combinação de instrumentação e subclassificação do agente Java para permitir a simulação desses tipos. Como isso funciona de maneira diferente do nosso mecanismo atual e este possui limitações diferentes e, como queremos reunir experiência e feedback do usuário, esse recurso precisou ser explicitamente ativado para estar disponível; isso pode ser feito através do mecanismo de extensão mockito, criando o arquivo que src/test/resources/mockito-extensions/org.mockito.plugins.MockMakercontém uma única linha:

mock-maker-inline

Depois de criar este arquivo, o Mockito usará automaticamente esse novo mecanismo e é possível:

 final class FinalClass {
   final String finalMethod() { return "something"; }
 }

 FinalClass concrete = new FinalClass(); 

 FinalClass mock = mock(FinalClass.class);
 given(mock.finalMethod()).willReturn("not anymore");

 assertThat(mock.finalMethod()).isNotEqualTo(concrete.finalMethod());

Nos marcos subsequentes, a equipe trará uma maneira programática de usar esse recurso. Identificaremos e forneceremos suporte para todos os cenários não imbatíveis. Fique atento e informe-nos o que você acha desse recurso!

Cavaleiro do Vento
fonte
14
Ainda recebo um erro: Não é possível zombar / espionar a classe android.content.ComponentName O Mockito não pode zombar / espionar porque: - classe final
IgorGanapolsky 9/17
3
Coloque o org.mockito.plugins.MockMakerarquivo na pasta correta.
WindRider 10/03/19
7
Também estou recebendo o erro mesmo depois de seguir o mencionado acima: O Mockito não pode zombar / espionar porque: - classe final
rcde0
8
@vCillusion, esta resposta não está relacionada ao PowerMock de forma alguma.
Linha
6
Segui essas instruções, mas ainda não consegui fazer isso funcionar. Alguém precisou fazer mais alguma coisa?
Franco
43

Você não pode zombar de uma aula final com o Mockito, pois não pode fazê-lo sozinho.

O que faço é criar uma classe não final para encerrar a classe final e usá-la como delegada. Um exemplo disso é a TwitterFactoryclasse, e esta é a minha classe ridicularizável:

public class TwitterFactory {

    private final twitter4j.TwitterFactory factory;

    public TwitterFactory() {
        factory = new twitter4j.TwitterFactory();
    }

    public Twitter getInstance(User user) {
        return factory.getInstance(accessToken(user));
    }

    private AccessToken accessToken(User user) {
        return new AccessToken(user.getAccessToken(), user.getAccessTokenSecret());
    }

    public Twitter getInstance() {
        return factory.getInstance();
    }
}

A desvantagem é que há muito código padrão; a vantagem é que você pode adicionar alguns métodos que podem estar relacionados aos negócios de aplicativos (como o getInstance que está recebendo um usuário em vez de um accessToken, no caso acima).

No seu caso, eu criaria uma RainOnTreesclasse não final que delega para a classe final. Ou, se você puder torná-lo não final, seria melhor.

Luigi R. Viggiano
fonte
6
+1. Se desejar, você pode usar algo como o Lombok @Delegatepara lidar com grande parte do clichê.
ruakh 29/06
2
@luigi você pode adicionar um trecho de código para o Junit como um exemplo. Tentei criar o Wrapper para a minha aula final, mas não consegui descobrir como testá-lo.
Incrível
31

adicione isso no seu arquivo gradle:

testImplementation 'org.mockito:mockito-inline:2.13.0'

esta é uma configuração para fazer o mockito funcionar com as classes finais

BennyP
fonte
1
Provavelmente deve usar "testImplementation" agora em vez de "testCompile". Gradle não gosta mais de "testCompile".
jwehrle
ótimo comentário, obrigado! editado para testImplementation. comentário original: testCompile 'org.mockito: mockito-inline: 2.13.0'
BennyP
2
Isso resulta em erro ao executar no Linux / OpenJDK 1.8:org.mockito.exceptions.base.MockitoInitializationException: Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.)
naXa 31/03/19
Funciona bem quando alternado para Oracle JDK 1.8
naXa 31/03/19
23

Use Powermock. Este link mostra como fazê-lo: https://github.com/jayway/powermock/wiki/MockFinal

Gábor Lipták
fonte
30
Eu acho que o PowerMock é como um daqueles remédios que devem sair apenas com base em "prescrição". No sentido de: deve-se deixar bem claro que o PowerMock tem muitos problemas; e que usá-lo é como o último recurso último; e deve ser evitado o máximo possível.
GhostCat
1
Por que você diz isso?
PragmaticProgrammer
Eu estava usando Powermockpara zombar das classes finais e dos métodos estáticos para aumentar minha cobertura oficialmente verificada Sonarqube. A cobertura foi de 0% desde o SonarQube, por qualquer motivo, não reconhece as classes que usam o Powermock em qualquer lugar dentro dele. Levei um tempo para mim e minha equipe para perceber isso a partir de algum tópico online. Portanto, esse é apenas um dos motivos para ter cuidado com o Powermock e provavelmente não o usar.
amer
16

Apenas para acompanhar. Adicione esta linha ao seu arquivo gradle:

testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9'

Eu tentei várias versões do mockito-core e mockito-all. Nenhum deles funciona.

Michael_Zhang
fonte
1
Para adicionar isso, uma coisa que observei foi que se você estiver usando o Powermock junto com o mockito; adicionar o arquivo de plug-in do mockmaker em 'src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker' não seria útil para zombar das classes finais. Em vez disso, adicionar uma dependência como mencionado por Michael_Zhang acima resolveria o problema de zombar das classes finais. Além disso, certifique-se que você está usando Mockito 2 em vez de Mockito1
dishooom
12

Eu acho que você fez isso finalporque deseja impedir que outras classes se estendam RainOnTrees. Como o Java efetivo sugere (item 15), há outra maneira de manter uma classe próxima da extensão sem torná-la final:

  1. Remova a finalpalavra - chave;

  2. Faça o seu construtor private. Nenhuma classe poderá estendê-lo porque não poderá chamar o superconstrutor;

  3. Crie um método de fábrica estático para instanciar sua classe.

    // No more final keyword here.
    public class RainOnTrees {
    
        public static RainOnTrees newInstance() {
            return new RainOnTrees();
        }
    
    
        private RainOnTrees() {
            // Private constructor.
        }
    
        public void startRain() {
    
            // some code here
        }
    }

Usando essa estratégia, você poderá usar o Mockito e manter sua classe fechada para extensão com pouco código padrão.

Flávio Faria
fonte
1
isso não funciona para métodos finais que, com o mockito 2, também podem ser zombados.
Łukasz Rzeszotarski 23/01/19
11

Eu tive o mesmo problema. Como a classe que eu estava tentando zombar era uma classe simples, simplesmente criei uma instância e a devolvi.

user1945457
fonte
2
Absolutamente, por que zombar de uma classe simples? Zombar é para interações 'caras': outros serviços, mecanismos, classes de dados etc.
StripLight
3
Se você criar uma instância disso, não poderá aplicar os métodos Mockito.verify posteriormente. O principal uso de zombarias é poder testar alguns de seus métodos.
Riroo
6

Faça uma tentativa:

Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));

Funcionou para mim. "SomeMockableType.class" é a classe pai do que você deseja zombar ou espionar, e someInstanceThatIsNotMockableOrSpyable é a classe real que você deseja zombar ou espionar.

Para mais detalhes, dê uma olhada aqui

sakis kaliakoudas
fonte
3
Deve-se notar que os delegados são muito diferentes da zombaria de espião nativo. Em um espião mockito nativo, "this" na referência da instância ao próprio espião (porque é usada subclasse). No entanto, no delegado, "this" será o objeto real someInstanceThatIsNotMockableOrSpyable. Não é o espião. Portanto, não há como retornar / verificar as funções de auto-chamada.
Dennis C
1
você pode colocar um exemplo?
Vishwa Ratna
5

Outra solução alternativa, que pode ser aplicada em alguns casos, é criar uma interface implementada por essa classe final, alterar o código para usar a interface em vez da classe concreta e zombar da interface. Isso permite separar o contrato (interface) da implementação (classe final). Obviamente, se o que você deseja é realmente vincular-se à classe final, isso não se aplicará.

andresp
fonte
5

Na verdade, há uma maneira que eu uso para espionagem. Funcionaria para você apenas se duas condições prévias fossem atendidas:

  1. Você usa algum tipo de DI para injetar uma instância da classe final
  2. A classe final implementa uma interface

Lembre-se do Item 16 do Effective Java . Você pode criar um wrapper (não final) e encaminhar todas as chamadas para a instância da classe final:

public final class RainOnTrees implement IRainOnTrees {
    @Override public void startRain() { // some code here }
}

public class RainOnTreesWrapper implement IRainOnTrees {
    private IRainOnTrees delegate;
    public RainOnTreesWrapper(IRainOnTrees delegate) {this.delegate = delegate;}
    @Override public void startRain() { delegate.startRain(); }
}

Agora você não apenas pode zombar de sua classe final, mas também espioná-la:

public class Seasons{
    RainOnTrees rain;
    public Seasons(IRainOnTrees rain) { this.rain = rain; };
    public void findSeasonAndRain(){
        rain.startRain();
   }
}

IRainOnTrees rain = spy(new RainOnTreesWrapper(new RainOnTrees()) // or mock(IRainOnTrees.class)
doNothing().when(rain).startRain();
new Seasons(rain).findSeasonAndRain();
xuesheng
fonte
5

No Mockito 3 e mais, tenho o mesmo problema e o corrigi a partir deste link

Zombe de classes e métodos finais com o Mockito, conforme a seguir

Antes que o Mockito possa ser usado para zombar das classes e métodos finais, ele precisa ser> configurado.

Precisamos adicionar um arquivo de texto ao diretório src / test / resources / mockito-extensions do projeto chamado org.mockito.plugins.MockMaker e adicionar uma única linha de texto:

mock-maker-inline

O Mockito verifica o diretório de extensões em busca de arquivos de configuração quando ele é carregado. Este arquivo permite a zombaria dos métodos e classes finais.

Sattar
fonte
4

Economia de tempo para pessoas que enfrentam o mesmo problema (Mockito + Final Class) no Android + Kotlin. Como no Kotlin, as aulas são finais por padrão. Encontrei uma solução em um dos exemplos do Google Android com o componente Arquitetura. Solução escolhida aqui: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample

Crie as seguintes anotações:

/**
 * This annotation allows us to open some classes for mocking purposes while they are final in
 * release builds.
 */
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class OpenClass

/**
 * Annotate a class with [OpenForTesting] if you want it to be extendable in debug builds.
 */
@OpenClass
@Target(AnnotationTarget.CLASS)
annotation class OpenForTesting

Modifique seu arquivo gradle. Veja o exemplo aqui: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/build.gradle

apply plugin: 'kotlin-allopen'

allOpen {
    // allows mocking for classes w/o directly opening them for release builds
    annotation 'com.android.example.github.testing.OpenClass'
}

Agora você pode anotar qualquer classe para torná-la aberta para teste:

@OpenForTesting
class RepoRepository 
Ozeetee
fonte
Isso funciona bem no build.gradle no nível do aplicativo, mas o que podemos fazer para obter isso no nível da biblioteca?
Sumit T
Você pode elaborar um pouco? Geralmente, use o padrão de fachada para conectar-se às bibliotecas. E zombe dessas classes de fachada para testar o aplicativo. Dessa forma, não precisamos zombar de nenhuma classe lib.
Ozeetee
3

Isso pode ser feito se você estiver usando o Mockito2, com o novo recurso de incubação que suporta a zombaria das classes e métodos finais.

Pontos principais a serem observados:
1. Crie um arquivo simples com o nome “org.mockito.plugins.MockMaker” e coloque-o em uma pasta chamada “mockito-extensions”. Essa pasta deve ser disponibilizada no caminho de classe.
2. O conteúdo do arquivo criado acima deve ser uma única linha, conforme indicado abaixo:
mock-maker-inline

As duas etapas acima são necessárias para ativar o mecanismo de extensão mockito e usar esse recurso de aceitação.

As classes de amostra são as seguintes: -

FinalClass.java

public final class FinalClass {

public final String hello(){
    System.out.println("Final class says Hello!!!");
    return "0";
}

}

Foo.java

public class Foo {

public String executeFinal(FinalClass finalClass){
    return finalClass.hello();
}

}

FooTest.java

public class FooTest {

@Test
public void testFinalClass(){
    // Instantiate the class under test.
    Foo foo = new Foo();

    // Instantiate the external dependency
    FinalClass realFinalClass = new FinalClass();

    // Create mock object for the final class. 
    FinalClass mockedFinalClass = mock(FinalClass.class);

    // Provide stub for mocked object.
    when(mockedFinalClass.hello()).thenReturn("1");

    // assert
    assertEquals("0", foo.executeFinal(realFinalClass));
    assertEquals("1", foo.executeFinal(mockedFinalClass));

}

}

Espero que ajude.

Artigo completo presente aqui, zombando do imbatível .

ksl
fonte
Você deve incluir a resposta aqui e não vincular a um site externo. Se o procedimento for longo, você poderá incluir uma visão geral.
Rgome
certifique-se de seguir as anotações são usadas quando zombando @RunWith (PowerMockRunner.class) @PrepareForTest ({} AFinalClass.class)
vCillusion
1
@vCillusion - O exemplo que eu mostrei usa apenas a API do Mockito2. Usando o recurso de ativação do Mockito2, é possível zombar das classes finais diretamente sem a necessidade de usar o Powermock.
ksl
2

Sim, mesmo problema aqui, não podemos zombar de uma aula final com o Mockito. Para ser exato, o Mockito não pode zombar / espionar o seguinte:

  • aulas finais
  • classes anônimas
  • tipos primitivos

Mas usar uma classe de invólucro me parece um preço muito alto a pagar; portanto, obtenha o PowerMockito.

Yu Chen
fonte
2

Eu acho que você precisa pensar mais em princípio. Em vez disso, a classe final usa a interface dele e a interface simulada.

Por esta:

 public class RainOnTrees{

   fun startRain():Observable<Boolean>{

        // some code here
   }
}

adicionar

interface iRainOnTrees{
  public void startRain():Observable<Boolean>
}

e zombe da sua interface:

 @Before
    fun setUp() {
        rainService= Mockito.mock(iRainOnTrees::class.java)

        `when`(rainService.startRain()).thenReturn(
            just(true).delay(3, TimeUnit.SECONDS)
        )

    }
Serg Burlaka
fonte
1

Por favor, olhe para o JMockit . Possui extensa documentação com muitos exemplos. Aqui você tem um exemplo de solução do seu problema (para simplificar, eu adicionei o construtor Seasonspara injetar uma RainOnTreesinstância simulada ):

package jmockitexample;

import mockit.Mocked;
import mockit.Verifications;
import mockit.integration.junit4.JMockit;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JMockit.class)
public class SeasonsTest {

    @Test
    public void shouldStartRain(@Mocked final RainOnTrees rain) {
        Seasons seasons = new Seasons(rain);

        seasons.findSeasonAndRain();

        new Verifications() {{
            rain.startRain();
        }};
    }

    public final class RainOnTrees {
        public void startRain() {
            // some code here
        }

    }

    public class Seasons {

        private final RainOnTrees rain;

        public Seasons(RainOnTrees rain) {
            this.rain = rain;
        }

        public void findSeasonAndRain() {
            rain.startRain();
        }

    }
}
Marcin
fonte
1

Soluções fornecidas por RC e Luigi R. Viggiano juntas são possivelmente a melhor idéia.

Embora Mockito não possa , por design, zombar das classes finais, a abordagem de delegação é possível . Isso tem suas vantagens:

  1. Você não é obrigado a mudar sua classe para não final, se é isso que sua API pretende em primeiro lugar (as aulas finais têm seus benefícios ).
  2. Você está testando a possibilidade de uma decoração em torno de sua API.

No seu caso de teste, você encaminha deliberadamente as chamadas para o sistema em teste. Por isso, por design, sua decoração não faz nada.

Portanto, o teste também pode demonstrar que o usuário pode decorar a API apenas em vez de estendê-la.

Em uma nota mais subjetiva: prefiro manter as estruturas no mínimo, e é por isso que JUnit e Mockito geralmente são suficientes para mim. De fato, restringir esse caminho às vezes me obriga a refatorar o bem também.

Neel
fonte
1

Se você está tentando executar o teste de unidade na pasta de teste , a solução principal está correta. Basta seguir adicionando uma extensão.

Mas se você deseja executá-lo com a classe relacionada ao Android, como contexto ou atividade, que está na pasta androidtest , a resposta é para você.

Allen
fonte
1

Adicione estas dependências para executar mockito com sucesso:

testImplementation 'org.mockito: mockito-core: 2.24.5'
testImplementation "org.mockito: mockito-inline: 2.24.5"

HandyPawan
fonte
0

Como outros já declararam, isso não funcionará imediatamente com o Mockito. Eu sugeriria o uso de reflexão para definir os campos específicos no objeto que está sendo usado pelo código em teste. Se você se encontra fazendo muito isso, pode agrupar essa funcionalidade em uma biblioteca.

Como um aparte, se você é o único que marca as aulas como final, pare de fazer isso. Eu me deparei com essa pergunta porque estou trabalhando com uma API em que tudo foi marcado como final para evitar minha necessidade legítima de extensão (zombaria), e gostaria que o desenvolvedor não assumisse que nunca precisaria estender a classe.

Tom N.
fonte
1
As classes de API pública devem estar abertas para extensão. Concordo totalmente. No entanto, em uma base de código privada, finaldeve ser o padrão.
ErikE
0

Para nós, foi porque excluímos o mockito-inline do koin-test. Um módulo gradle realmente precisava disso e, por motivo, apenas falhou nas compilações de versão (compilações de depuração no IDE funcionaram) :-P

kenyee
fonte
0

Para a aula final, adicione abaixo a simulação e chame estática ou não estática.

1- adicione isso no nível da classe @SuppressStatucInitializationFor (value = {nome da classe com pacote})
2- PowerMockito.mockStatic (classname.class) zombará da classe
3- e use sua instrução when para retornar o objeto mock ao chamar o método desta classe.

Aproveitar

Manas
fonte
-5

Não tentei final, mas para privado, usando a reflexão remova o modificador trabalhado! ter verificado mais, não funciona para final.

lwpro2
fonte
isso não está respondendo a pergunta
Sanjit Kumar Mishra