Como setar dado privado em testes no Spring
Quando não se está fazendo testes de integração, podem ocorrer cenários que você necessite setar um atributo privado de uma classe a ser testada onde não é possível injetar valor via mock tendo em vista que provavelmente está se usando apenas Mockito + JUnit.
Para exemplificar, pense que temos uma @Service responsável por gerar uma senha com X número de caracteres onde a variável “length” é responsável por inferir o número de caracteres.
@Service
public class SecurityService {
@Value("length_password")
private Integer length;
public String getPassword() {
Random random = new SecureRandom();
IntStream specialChars = random.ints(length, 33, 45);
Stream<Character> characterStream = specialChars.mapToObj(data -> (char) data);
return characterStream.collect(Collector.of(
StringBuilder::new,
StringBuilder::append,
StringBuilder::append,
StringBuilder::toString));
}
}
Se perceber verá que existe nesta variável que informa o tamanho da senha uma externalização de configuração. Isso é uma boa prática no sentido que possibilita mudar configurações da aplicação em relação a seu comportamento sem necessidade de mudança de código.
O problema é que no caso de testar de forma unitária não é possível realizar o mock deste atributo. Veja uma classe de teste e o erro de execução pela variável não possuir valor:
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class SecurityServiceTest {
@InjectMocks
private SecurityService securityService = new SecurityService();
@Test
public void testGetPassword() {
String password = securityService.getPassword();
assertNotNull(password);
int lengthPassword = 6;
assertEquals(6, password.length());
}
}
Execução do Teste:
java.lang.NullPointerException
at com.example.demo.service.SecurityService.getPassword(SecurityService.java:22)
at com.example.demo.service.SecurityServiceTest.testGetPassword(SecurityServiceTest.java:17)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Como já era esperado, um NullPointerException ocorre.
Para resolver esta situação, existe a classe ReflectionTestUtils que via reflexão trata estes tipos de cenário durante a execução do teste.
Basta aplicar a seguinte mudança:
@BeforeEach
public void setUp() {
ReflectionTestUtils.setField(securityService, "length", 6);
}
O código completo ficaria:
@ExtendWith(MockitoExtension.class)
public class SecurityServiceTest {
@InjectMocks
private SecurityService securityService = new SecurityService();
@BeforeEach
public void setUp() {
ReflectionTestUtils.setField(securityService, "length", 6);
}
@Test
public void testGetPassword() {
String password = securityService.getPassword();
assertNotNull(password);
int lengthPassword = 6;
assertEquals(6, password.length());
}
}
Pronto! Desta forma a variável na instância utilizada passa a ter o valor inferido possibilitando o execução com sucesso do teste.
Espero ter ajudado!