Atualmente podemos dizer que o Spring Framework é a escolha de praticamente todos os desenvolvedores Java que estão iniciando em um novo projeto, e não é pra menos pois esse framework conseguiu reunir em um lugar só toda a facilidade para desenvolvimento de aplicações Web com sua criação de serviços REST, acesso a dados, gerenciamento de filas e acesso a outros serviços, tornando-se um padrão entre seus desenvolvedores.
E além de tudo que o Spring oferece, algo que existe no framework desde sua criação e que ajuda e muito os desenvolvedores é a sua facilidade de trabalhar com injeção de dependências, pois ele possui um container de inversão de controle que permite facilmente injetar dependências em uma classe usando a famosa anotação mágica @Autowired.
1
2
3
4
5
6
7
8
9
public class MyClass {
    @Autowired
    private MyDependency myDependency;
    public void foo() {
        myDependency.bar();
    }
}
Porém, junto com essa facilidade vem um problema que trava muitos desenvolvedores no momento de criar testes unitários:
Como criar um mock de uma classe injetada por @Autowired.
Realmente, como é o próprio Spring quem faz a injeção, fica complicado de fazer esse mock, pois não é legal depender do Framework para fazer os testes unitários, já que em princípio um teste unitário deve depender somente da classe/arquivo que está sendo testado.
E para resolver esse problema é claro que temos alternativas, que vou apresentar a seguir usando como exemplo um projeto de teste encontrado por completo aqui.
Este projeto é uma aplicação web SpringBoot simples que a partir de seu nome e data de nascimento exibe uma mensagem dizendo quando será seu próximo aniversário. Nos testes iremos focar na classe BirthdayService que calcula quando será a próxima data de aniversário e constrói a mensagem, e na classe LocalDateService injetada pelo Spring e responsável por prover a data atual.
O projeto está usando o JUnit 5 que já faz parte da configuração inicial do SpringBoot.
Observação: Se você não sabe o que é um mock aconselho e ler primeiro esse meu post .
Injetando no construtor ou setter
Tudo bem, eu acredito que essa não seja a alternativa que você esteja procurando, mas ela funciona muito bem!
Eu sei que a maioria das pessoas usa o @Autowired direto na declaração da dependência para facilitar a escrita do código, como podemos ver a seguir.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Service
public class BirthdayService {
    
    public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd/MM/yyyy");
    
    @Autowired
    private LocalDateService localDateService;
    
    public String getBirthdayMessage(String name, LocalDate bornDay) {
        var nextBirthDay = getNextBirthday(bornDay);
        
        var message = new StringBuilder()
                .append("Olá ")
                .append(name)
                .append(", seu próximo aniversário será no dia ")
                .append(DATE_FORMAT.format(nextBirthDay))
                .append("!");
        
        return message.toString();
    }
    private LocalDate getNextBirthday(LocalDate bornDay) {
        var currentDate = localDateService.getCurrentDate();
        var birthday = LocalDate.of(currentDate.getYear(), bornDay.getMonth(), bornDay.getDayOfMonth());
        if (currentDate.isAfter(birthday)) {
            birthday = birthday.plusYears(1);
        }
        return birthday;
    }
}
Mas é exatamente essa forma de escrita que causa o todo o problema de utilizar um mock nos testes unitários, o que pode ser resolvido passando a dependência via construtor ou via setter, como vemos abaixo.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class BirthdayService {
    
    public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd/MM/yyyy");
    private LocalDateService localDateService;
    
    @Autowired
    public void setLocalDateService(LocalDateService localDateService) {
        this.localDateService = localDateService;
    }
    public String getBirthdayMessage(String name, LocalDate bornDay) {
        //...
    }
}
Usando o @Autowired no método de setter da dependência irá fazer com que o Spring consiga realizar a injeção normalmente, e nos permite escrever o teste passando o mock por parâmetro para a classe testada.
Neste exemplo vou usar o Mockito como biblioteca de mock.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.time.LocalDate;
import org.junit.jupiter.api.Test;
public class BirthdayServiceTest {
    @Test
    public void shouldReturnBirthdayMessage() {
        var name    = "Murilo";
        var bornDay = LocalDate.of(1989, 7, 10);
        LocalDateService localDateServiceMock = mock(LocalDateService.class);
        when(localDateServiceMock.getCurrentDate()).thenReturn(LocalDate.of(2021, 3, 15));
        BirthdayServiceAlternative birthdayService = new BirthdayServiceAlternative();
        birthdayService.setLocalDateService(localDateServiceMock); //atribuindo o mock
        var message = birthdayService.getBirthdayMessage(name, bornDay);
        assertEquals(message, "Olá Murilo, seu próximo aniversário será no dia 10/07/2021!");
    }
}
E assim resolvemos de maneira rápida e fácil o problema de mock.
Usando o Mockito extension
Mas eu sei que a realidade de muitos projetos é bem diferente e a abordagem mais utilizada é adicionar o @Autowired diretamente no atributo, por isso temos que recorrer a outros meios para conseguir prover um mock.
1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class BirthdayService {
    
    public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd/MM/yyyy");
    
    @Autowired
    private LocalDateService localDateService;
    
    public String getBirthdayMessage(String name, LocalDate bornDay) {
        //...
    }
}
Pensando nisso algumas bibliotecas de mock começaram a adicionar recursos extras permitindo que o teste fosse executado utilizando mais recursos dessas bibliotecas, geralmente usando a anotação @RunWith, fazendo com que essa injeção de mock acontecesse de forma transparente, e partir do JUnit 5 esse tipo de recurso foi incorporado ao framework usando o nome de extensions, que agora permitem fazer isso de forma super simples.
Aqui vamos usar a extension provida pelo Mockito 1.10.19, e vamos anotar o nosso mock com a anotação @Mock e nossa classe a ser testado com o @InjectMocks, que irá injetar o mock.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@ExtendWith(MockitoExtension.class)
public class BirthdayServiceWithMockitoExtensionTest {
    
    @Mock
    private LocalDateService localDateServiceMock;
    
    @InjectMocks
    private BirthdayService birthdayService;
    @Test
    public void shouldReturnBirthdayMessage() {
        var name    = "Murilo";
        var bornDay = LocalDate.of(1989, 7, 10);
        when(localDateServiceMock.getCurrentDate()).thenReturn(LocalDate.of(2021, 3, 15));
        var message = birthdayService.getBirthdayMessage(name, bornDay);
        assertEquals("Olá Murilo, seu próximo aniversário será no dia 10/07/2021!", message);
    }
}
E pronto. Muito simples não é mesmo?
E não é somente o Mockito que provê uma extension.
Usando o EasyMock extension
Além do Mockito, o EasyMock é uma das libs de mock mais utilizadas em Java, e também provê o mecanismo de extension para injeção de mocks.
A forma de utilizar é praticamente idêntica ao Mockito, com a diferença que a classe a ser testada recebe a anotação @TestSubject para que sejam injetadas as dependências, e é claro que o EasyMock possui métodos diferentes para declaração dos comportamentos esperados.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import org.easymock.EasyMockExtension;
import org.easymock.Mock;
import org.easymock.TestSubject;
//... outros imports
@ExtendWith(EasyMockExtension.class)
public class BirthdayServiceWithEasyMockExtensionTest {
    
    @Mock
    private LocalDateService localDateServiceMock;
    
    @TestSubject
    private BirthdayService birthdayService;
    @Test
    public void shouldReturnBirthdayMessage() {
        var name    = "Murilo";
        var bornDay = LocalDate.of(1989, 7, 10);
        expect(localDateServiceMock.getCurrentDate()).andReturn(LocalDate.of(2021, 3, 15));
        replay(localDateServiceMock);
        var message = birthdayService.getBirthdayMessage(name, bornDay);
        assertEquals("Olá Murilo, seu próximo aniversário será no dia 10/07/2021!", message);
    }
}
Muito fácil não é mesmo?
Neste exemplo usamos a versão 4.2 do EasyMock.
Usando o Spring Framework
Mas e se estivermos usando versões antigas do JUnit, Mockito ou EasyMock, onde essas possibilidades não existem, como poderemos usar esses mocks?
Talvez foi pensando nisso que o Spring passou a prover uma forma de também injetar os mocks em nossos testes unitários, que não é tão prática como as mostradas anteriormente, porém também funciona muito bem.
O Spring disponibiliza desde muito tempo a classe utilitária ReflectionTestUtils que permite injetarmos os mocks usando reflections, o que com um pouco mais de trabalho também resolve o problema.
Nesse caso nós criamos uma instância da classe a ser testada e injetamos os mocks utilizando o método setField da classe ReflectionTestUtils, passando como parâmetro o objeto onde será injetada as dependências, o nome do atributo dentro da classe testada e o mock.
Vou usar o Mockito novamente como lib de mock aqui.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import org.springframework.test.util.ReflectionTestUtils;
public class BirthdayServiceWithReflectionTestUtilsTest {
    
    private LocalDateService localDateServiceMock;
    
    @BeforeEach
    public void setUp() {
        localDateServiceMock = mock(LocalDateService.class);
    }
    @Test
    public void shouldReturnBirthdayMessage() {
        var name    = "Murilo";
        var bornDay = LocalDate.of(1989, 7, 10);
        when(localDateServiceMock.getCurrentDate()).thenReturn(LocalDate.of(2021, 3, 15));
        BirthdayService birthdayService = buildBirthdayService();
        var message = birthdayService.getBirthdayMessage(name, bornDay);
        assertEquals("Olá Murilo, seu próximo aniversário será no dia 10/07/2021!", message);
    }
    
    private BirthdayService buildBirthdayService() {
        BirthdayService birthdayService = new BirthdayService();
        
        ReflectionTestUtils.setField(birthdayService, "localDateService", localDateServiceMock);
        
        return birthdayService;
    }
}
Perceba que foi necessário alguns códigos a mais para inicializar o objeto a ser testado com as dependências e também criar o mock toda vez que um teste for executado (@BeforeEach), mas assim conseguimos executar nosso teste unitário normalmente.
E com essa última dica eu encerro esse post na esperança que te ajude em suas próximas escritas de testes unitários, e caso tenha alguma dúvida ou sugestão por favor deixe nos comentários.
Projeto completo no Github.
Até mais.
