Qual é a diferença entre @Mock
e @InjectMocks
no framework Mockito?
Qual é a diferença entre @Mock
e @InjectMocks
no framework Mockito?
Respostas:
@Mock
cria uma farsa. @InjectMocks
cria uma instância da classe e injeta as simulações criadas com as anotações @Mock
(ou @Spy
) nessa instância.
Observe que você deve usar @RunWith(MockitoJUnitRunner.class)
ou Mockito.initMocks(this)
para inicializar essas simulações e injetá-las.
@RunWith(MockitoJUnitRunner.class)
public class SomeManagerTest {
@InjectMocks
private SomeManager someManager;
@Mock
private SomeDependency someDependency; // this will be injected into someManager
//tests...
}
Este é um código de exemplo de como @Mock
e @InjectMocks
funciona.
Digamos que temos Game
e Player
classe.
class Game {
private Player player;
public Game(Player player) {
this.player = player;
}
public String attack() {
return "Player attack with: " + player.getWeapon();
}
}
class Player {
private String weapon;
public Player(String weapon) {
this.weapon = weapon;
}
String getWeapon() {
return weapon;
}
}
Como você vê, a Game
classe precisa Player
executar um attack
.
@RunWith(MockitoJUnitRunner.class)
class GameTest {
@Mock
Player player;
@InjectMocks
Game game;
@Test
public void attackWithSwordTest() throws Exception {
Mockito.when(player.getWeapon()).thenReturn("Sword");
assertEquals("Player attack with: Sword", game.attack());
}
}
Mockito irá zombar de uma classe Player e seu comportamento usando when
e thenReturn
método. Por fim, usar o @InjectMocks
Mockito colocará isso Player
em Game
.
Observe que você nem precisa criar um new Game
objeto. Mockito irá injetá-lo para você.
// you don't have to do this
Game game = new Game(player);
Também obteremos o mesmo comportamento usando a @Spy
anotação. Mesmo se o nome do atributo for diferente.
@RunWith(MockitoJUnitRunner.class)
public class GameTest {
@Mock Player player;
@Spy List<String> enemies = new ArrayList<>();
@InjectMocks Game game;
@Test public void attackWithSwordTest() throws Exception {
Mockito.when(player.getWeapon()).thenReturn("Sword");
enemies.add("Dragon");
enemies.add("Orc");
assertEquals(2, game.numberOfEnemies());
assertEquals("Player attack with: Sword", game.attack());
}
}
class Game {
private Player player;
private List<String> opponents;
public Game(Player player, List<String> opponents) {
this.player = player;
this.opponents = opponents;
}
public int numberOfEnemies() {
return opponents.size();
}
// ...
Isso porque Mockito irá verificar a Type Signature
classe Game, que é Player
e List<String>
.
Na sua classe de teste, a classe testada deve ser anotada @InjectMocks
. Isso informa ao Mockito em qual classe injetar zombarias:
@InjectMocks
private SomeManager someManager;
A partir de então, podemos especificar quais métodos ou objetos específicos dentro da classe, neste caso SomeManager
, serão substituídos por zombarias:
@Mock
private SomeDependency someDependency;
Neste exemplo, SomeDependency
dentro da SomeManager
classe será ridicularizado.
@Mock
anotação zomba do objeto em questão.
@InjectMocks
A anotação permite injetar no objeto subjacente as diferentes (e relevantes) simulações criadas por @Mock
.
Ambos são complementares.
@InjectMocks
para construir essa classe e espioná-la também.
Por exemplo
@Mock
StudentDao studentDao;
@InjectMocks
StudentService service;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
Aqui precisamos da classe DAO para a classe de serviço. Portanto, zombamos e injetamos na instância da classe de serviço. Da mesma forma, no framework Spring, todos os beans @Autowired podem ser zombados pelo @Mock nas jUnits e injetados no seu bean através do @InjectMocks.
MockitoAnnotations.initMocks(this)
O método inicializa essas simulações e as injeta em todos os métodos de teste, portanto, ele precisa ser chamado no setUp()
método.
Uma "estrutura de zombaria", na qual o Mockito se baseia, é uma estrutura que permite criar objetos Mock (em termos antigos, esses objetos podem ser chamados de desvios, pois funcionam como desvios para a funcionalidade dependente) Em outras palavras, um mock objeto é usado para imitar o objeto real do qual seu código depende, você cria um objeto proxy com a estrutura de simulação. Ao usar objetos simulados em seus testes, você basicamente passa do teste de unidade normal para o teste de integração
O Mockito é uma estrutura de teste de código aberto para Java lançada sob a licença MIT, é uma "estrutura de zombaria", que permite escrever belos testes com API limpa e simples. Existem muitas estruturas de simulação diferentes no espaço Java, no entanto, existem essencialmente dois tipos principais de estruturas de objetos de simulação, aquelas implementadas via proxy e aquelas implementadas via remapeamento de classe.
Estruturas de injeção de dependência como o Spring permitem que você injete seus objetos proxy sem modificar nenhum código, o objeto simulado espera que um determinado método seja chamado e retornará um resultado esperado.
A @InjectMocks
anotação tenta instanciar a instância do objeto de teste e injeta campos anotados com @Mock
ou @Spy
em campos particulares do objeto de teste.
MockitoAnnotations.initMocks(this)
chame, redefina o objeto de teste e reinicialize as simulações; lembre-se de tê-lo em sua @Before
/ @BeforeMethod
anotação.
Uma vantagem que você obtém com a abordagem mencionada pelo @Tom é que você não precisa criar construtores no SomeManager e, portanto, limitar os clientes a instanciar.
@RunWith(MockitoJUnitRunner.class)
public class SomeManagerTest {
@InjectMocks
private SomeManager someManager;
@Mock
private SomeDependency someDependency; // this will be injected into someManager
//You don't need to instantiate the SomeManager with default contructor at all
//SomeManager someManager = new SomeManager();
//Or SomeManager someManager = new SomeManager(someDependency);
//tests...
}
Se é uma boa prática ou não, depende do design do seu aplicativo.
Muitas pessoas têm dado um grande explicação aqui sobre @Mock
vs @InjectMocks
. Eu gosto, mas acho que nossos testes e aplicativos devem ser escritos de uma maneira que não precisemos usar @InjectMocks
.
Referência para leituras adicionais com exemplos: https://tedvinke.wordpress.com/2014/02/13/mockito-why-you-should-not-use-injectmocks-annotation-to-autowire-fields/
@Mock
é usado para declarar / zombar das referências dos beans dependentes, enquanto @InjectMocks
é usado para zombar do bean para o qual o teste está sendo criado.
Por exemplo:
public class A{
public class B b;
public void doSomething(){
}
}
teste para classe A
:
public class TestClassA{
@Mocks
public class B b;
@InjectMocks
public class A a;
@Test
public testDoSomething(){
}
}
A anotação @InjectMocks pode ser usada para injetar campos simulados em um objeto de teste automaticamente.
No exemplo abaixo, @InjectMocks usou para injetar o dataMap simulado na dataLibrary.
@Mock
Map<String, String> dataMap ;
@InjectMocks
DataLibrary dataLibrary = new DataLibrary();
@Test
public void whenUseInjectMocksAnnotation_() {
Mockito.when(dataMap .get("aData")).thenReturn("aMeaning");
assertEquals("aMeaning", dataLibrary .getMeaning("aData"));
}
Embora as respostas acima tenham sido abordadas, apenas tentei adicionar detalhes minuciosos que vejo faltando. A razão por trás deles (The Why).
Ilustração:
Sample.java
---------------
public class Sample{
DependencyOne dependencyOne;
DependencyTwo dependencyTwo;
public SampleResponse methodOfSample(){
dependencyOne.methodOne();
dependencyTwo.methodTwo();
...
return sampleResponse;
}
}
SampleTest.java
-----------------------
@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassA.class})
public class SampleTest{
@InjectMocks
Sample sample;
@Mock
DependencyOne dependencyOne;
@Mock
DependencyTwo dependencyTwo;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
public void sampleMethod1_Test(){
//Arrange the dependencies
DependencyResponse dependencyOneResponse = Mock(sampleResponse.class);
Mockito.doReturn(dependencyOneResponse).when(dependencyOne).methodOne();
DependencyResponse dependencyTwoResponse = Mock(sampleResponse.class);
Mockito.doReturn(dependencyOneResponse).when(dependencyTwo).methodTwo();
//call the method to be tested
SampleResponse sampleResponse = sample.methodOfSample()
//Assert
<assert the SampleResponse here>
}
}