In the setup in my test, the line:
when(kc.getToken()).thenReturn(token);
does not work and I get the following error from the ServiceImplementation
:
java.lang.NullPointerException: Cannot invoke "services.KeycloakService.getToken()" because "this.kc" is null
I am using the following dependencies:
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mockito:mockito-core:5.14.2'
testImplementation 'org.mockito:mockito-junit-jupiter:5.14.2'
WHAT I TRIED:
testMockInjection
: all tests are green.testUpdate
: I see that the code from the Service implementation stops in the lineString zaToken = kc.getToken
and gives the aforementioned error
@ExtendWith(MockitoExtension.class)
, @BeforeEach
and @InjectMocks
which work in my other tests.However, I also tried MockitoAnnotations.initMocks(this)
and the error remained.
SERVICE IMPLEMENTATION CODE
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
...
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
...
import services.AasService;
import services.KeycloakService;
@Service
public class AasServiceImpl implements AasService{
private final RestTemplate restTemplate;
@Autowired
private KeycloakService kc;
@Value("${ass.server}")
private String aasUri;
@Value("${ass.id}")
private String aasIdShort;
public AasServiceImpl (RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public void update(String applicationUri, String nodeId, JSONObject value) {
System.out.println(applicationUri);
System.out.println(nodeId);
System.out.println(value);
String submodelElementValueId = applicationUri.replace(":", "_") + nodeId.replace("i=", "");
System.out.println(submodelElementValueId);
if (kc instanceof KeycloakServiceImpl) {
System.out.println("This is original service.");
} else {
System.out.println("This is not the original service.");
//this is the option printed
}
//problematic line is the following
String zaToken = kc.getToken();
//this is not printed in the code
System.out.println(zaToken);
try {
updateValue("DefectDetectionSkill", "Inputs", submodelElementValueId, value, zaToken);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
TEST CODE:
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
...
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
...
import org.junit.jupiter.api.BeforeEach;
import services.KeycloakService; //this is an interface
@ExtendWith(MockitoExtension.class)
public class AasServiceImplTest {
@Mock
private KeycloakService kc;
@Mock
private RestTemplate restTemplate;
@InjectMocks
private AasServiceImpl aasService;
@BeforeEach
void setUp() {
String token = "mockToken";
when(kc.getToken()).thenReturn(token);
ResponseEntity<String> response= new ResponseEntity<>("", HttpStatus.OK);
when(restTemplate.exchange(anyString(), any(), any(), eq(String.class))).thenReturn(response);
System.out.println("set up runs");
//this message is printed
}
/*
@Test
void testMockInjection() {
assertNotNull(kc, "KeycloakService mock is not injected!");
assertNotNull(restTemplate, "RestTemplate mock is not injected!");
assertNotNull(aasService, "AasServiceImpl is not initialized!");
}
*/
@Test
void testUpdate() {
//Arrange
String applicationUri = "http://mock_uri";
String nodeId="i=ioanna";
JSONObject value = new JSONObject();
value.put("Value", 1234);
//Act
aasService.update(applicationUri, nodeId , value);
System.out.println("Value: " + value.get("Value"));
//Assert
assertNotNull(kc, "KeycloakService mock is not injected!");
//verify(kc, times(1)).getToken();
//verify(restTemplate, atLeastOnce()).exchange(anyString(), any(), any(), eq(String.class));
}
It seems like @InjectMock
is failing to mix field injection and constructor argument injection. Either have both the restTemplate
and kc
fields autowired:
@Service
public class AasServiceImpl implements AasService {
// No explicit constructor!
@Autowired
private RestTemplate restTemplate;
@Autowired
private KeycloakService kc;
...
Or have them both as constructor arguments:
@Service
public class AasServiceImpl implements AasService {
private RestTemplate restTemplate;
private KeycloakService kc;
public AasServiceImpl (RestTemplate restTemplate, KeycloakService kc) {
this.restTemplate = restTemplate;
this.kc = kc;
}
...