Inicializando objetos fictícios - MockIto


122

Existem muitas maneiras de inicializar um objeto mock usando MockIto. Qual é a melhor forma entre essas?

1

 public class SampleBaseTestCase {

   @Before public void initMocks() {
       MockitoAnnotations.initMocks(this);
   }

2

@RunWith(MockitoJUnitRunner.class)

[EDITAR] 3.

mock(XXX.class);

sugira-me se houver outras maneiras melhores do que estas

Respostas:


153

Para a inicialização de simulações , usar o runner ou MockitoAnnotations.initMockssão soluções estritamente equivalentes. Do javadoc do MockitoJUnitRunner :

JUnit 4.5 runner initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object) is not necessary. Mocks are initialized before each test method.


A primeira solução (com MockitoAnnotations.initMocks) pode ser usada quando você já configurou um runner específico ( SpringJUnit4ClassRunnerpor exemplo) em seu caso de teste.

A segunda solução (com o MockitoJUnitRunner) é a mais clássica e minha favorita. O código é mais simples. Usando um corredor oferece a grande vantagem de validação automática do uso do quadro (descrito por @ David Wallace em esta resposta ).

Ambas as soluções permitem compartilhar os mocks (e espiões) entre os métodos de teste. Juntamente com o @InjectMocks, eles permitem escrever testes de unidade muito rapidamente. O código de simulação padrão é reduzido, os testes são mais fáceis de ler. Por exemplo:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock(name = "database") private ArticleDatabase dbMock;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @InjectMocks private ArticleManager manager;

    @Test public void shouldDoSomething() {
        manager.initiateArticle();
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        manager.finishArticle();
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Prós: o código é mínimo

Contras: magia negra. IMO é principalmente devido à anotação @InjectMocks. Com esta anotação "você perde a dor do código" (veja os ótimos comentários de @Brice )


A terceira solução é criar seu mock em cada método de teste. Ele permite, conforme explicado por @mlk em sua resposta, ter um " teste independente ".

public class ArticleManagerTest {

    @Test public void shouldDoSomething() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Prós: você demonstra claramente como sua API funciona (BDD ...)

Contras: há mais código clichê. (A criação de mocks)


Minha recomendação é um compromisso. Use a @Mockanotação com @RunWith(MockitoJUnitRunner.class), mas não use @InjectMocks:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock private ArticleDatabase database;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @Test public void shouldDoSomething() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then 
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Prós: você demonstra claramente como sua API funciona (como o my ArticleManageré instanciado). Nenhum código clichê.

Contras: o teste não é independente, menos dor de código


Porém, tenha cuidado, as anotações são úteis, mas não protegem você contra a criação de um design OO pobre (ou degradá-lo). Pessoalmente, embora esteja feliz em reduzir o código clichê, eu perco a dor do código (ou PITA) que é o gatilho para mudar o design para um melhor, então eu e a equipe estamos prestando atenção ao design OO. Acho que seguir o design OO com princípios como o design SOLID ou as idéias GOOS é muito mais importante do que escolher como instanciar mocks.
Brice

1
(continuação) Se você não vê como este objeto é criado, você não se preocupa com isso, e futuros programadores podem não reagir bem se uma nova funcionalidade for adicionada. De qualquer forma, isso é discutível em ambos os sentidos, estou apenas dizendo para ter cuidado com isso.
Brice

6
NÃO É CORRETO que esses dois sejam equivalentes. NÃO É VERDADE que um código mais simples é a única vantagem de usar MockitoJUnitRunner. Para obter mais informações sobre as diferenças, consulte a pergunta em stackoverflow.com/questions/10806345/… e minha resposta a ela.
Dawood ibn Kareem

2
@Gontard Sim, com certeza as dependências são visíveis, mas eu vi o código dar errado usando essa abordagem. Sobre o uso do Collaborator collab = mock(Collaborator.class), na minha opinião, esta forma é certamente uma abordagem válida. Embora isso possa tender a ser prolixo, você pode obter maior compreensão e refatoração dos testes. Ambas as formas têm seus prós e contras, ainda não decidi qual abordagem é melhor. Amyway, sempre é possível escrever uma porcaria e provavelmente depende do contexto e do codificador.
Brice,

1
@mlk concordo totalmente com você. Meu inglês não é muito bom e não tem nuances. Meu objetivo era insistir na palavra UNIT.
gontard de

30

Existe agora (a partir da v1.10.7) uma quarta maneira de instanciar mocks, que é usando uma regra JUnit4 chamada MockitoRule .

@RunWith(JUnit4.class)   // or a different runner of your choice
public class YourTest
  @Rule public MockitoRule rule = MockitoJUnit.rule();
  @Mock public YourMock yourMock;

  @Test public void yourTestMethod() { /* ... */ }
}

JUnit procura por subclasses de TestRule anotadas com @Rule e as usa para agrupar as instruções de teste que o Runner fornece . O resultado disso é que você pode extrair métodos @Before, métodos @After e até mesmo tentar ... capturar wrappers em regras. Você pode até interagir com eles de dentro do seu teste, da maneira que ExpectedException faz.

MockitoRule se comporta quase exatamente como MockitoJUnitRunner , exceto que você pode usar qualquer outro runner, como Parameterized (que permite que seus construtores de teste recebam argumentos para que seus testes possam ser executados várias vezes) ou o executor de teste do Robolectric (para que seu classloader possa fornecer substituições Java para classes nativas Android). Isso o torna estritamente mais flexível para uso nas versões JUnit e Mockito recentes.

Em suma:

  • Mockito.mock(): Invocação direta sem suporte de anotação ou validação de uso.
  • MockitoAnnotations.initMocks(this): Suporte de anotação, sem validação de uso.
  • MockitoJUnitRunner: Suporte de anotação e validação de uso, mas você deve usar esse runner.
  • MockitoRule: Suporte de anotação e validação de uso com qualquer executor JUnit.

Veja também: Como funciona o JUnit @Rule?


3
Em Kotlin, a regra é a seguinte:@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
Cristan

10

Existe uma maneira legal de fazer isso.

  • Se for um teste de unidade, você pode fazer o seguinte:

    @RunWith(MockitoJUnitRunner.class)
    public class MyUnitTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Test
        public void testSomething() {
        }
    }
    
  • EDITAR: Se for um teste de integração, você pode fazer isso (não se destina a ser usado dessa forma com Spring. Apenas mostre que você pode inicializar simulações com diferentes Runners):

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("aplicationContext.xml")
    public class MyIntegrationTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Before
        public void setUp() throws Exception {
              MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testSomething() {
        }
    }
    

1
Se o MOCK também estiver envolvido nos testes de integração, isso fará sentido?
VinayVeluri

2
na verdade não vai, você está certo. Eu só queria mostrar as possibilidades do Mockito. Por exemplo, se você usar RESTFuse, terá que usar o runner deles para inicializar mocks com MockitoAnnotations.initMocks (this);
emd

8

MockitoAnnotations & the runner foram bem discutidos acima, então vou jogar meus dois centavos para os não amados:

XXX mockedXxx = mock(XXX.class);

Eu uso isso porque acho que é um pouco mais descritivo e prefiro (não banir totalmente) os testes de unidade não usar variáveis ​​de membro, pois gosto que meus testes sejam (o máximo que podem) independentes.


Existe alguma outra vantagem sobre o uso de mock (XX.class), exceto tornar o caso de teste independente?
VinayVeluri

Não que eu saiba.
Michael Lloyd Lee mlk

3
Menos mágica para ter que entender para ler o teste. Você declara a variável e dá a ela um valor - sem anotações, reflexão etc.
Karu

2

Um pequeno exemplo para JUnit 5 Jupiter, o "RunWith" foi removido, agora você precisa usar as extensões usando a anotação "@ExtendWith".

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}

0

As outras respostas são ótimas e contêm mais detalhes se você quiser / precisar delas.
Além desses, gostaria de adicionar um TL; DR:

  1. Prefiro usar
    • @RunWith(MockitoJUnitRunner.class)
  2. Se você não puder (porque você já usa um corredor diferente), prefira usar
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. Semelhante a (2), mas você não deve usar mais isto:
    • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
  4. Se você quiser usar um mock em apenas um dos testes e não quiser expô-lo a outros testes na mesma classe de teste, use
    • X x = mock(X.class)

(1) e (2) e (3) são mutuamente exclusivos.
(4) pode ser usado em combinação com os outros.

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.