I'm trying to write my Unit Tests for a class which extends another. I'm calling a superclass method in another child class method :
The child class
#RestController
#RequestMapping(PUBLIC_BASE_URL)
public class RequeteSASRestController extends BaseController {
private static final Logger LOGGER = LogManager.getLogger();
private final RequeteSASService requeteSASService;
#Inject
public RequeteSASRestController(
final RequeteSASService pRequeteSASJdbcService) {
requeteSASService = pRequeteSASJdbcService;
}
#ApiOperation(value = "Lecture d'une liste des requetes SAS", responseContainer = "List")
#RequestMapping(value = "/statistiques/requeteSAS", method = RequestMethod.GET)
public ResponseEntity<SimpleServiceResponse<List<RequeteSAS>>> readListeRequeteSas() {
LOGGER.debug(" readListeRequeteSas() ->");
String remoteUser=super.getCurrentUserLogin();
return this.ok(requeteSASService.readListeRequeteSas(remoteUser));
}
The superclass
public class BaseController {
public String getCurrentUserLogin() {
//Some things
}
And finally here's my test class :
public class RequeteSASRestTest extends Mockito{
private RequeteSAS requeteSASJdbc1;
private RequeteSAS requeteSASJdbc2;
private RequeteSAS requeteSASJdbc1suppr;
/** Service RequeteSAS */
private RequeteSASService mockRequeteService;
/** Rest Controller. */
private RequeteSASRestController restController;
// Before : mock des services
#Before
public void beforeTests() throws FileNotFoundException {
clearSecurityContext();
mockRequeteService = mock(RequeteSASService.class);
restController = Mockito.spy(new RequeteSASRestController(mockRequeteService));
requeteSASJdbc1 = RequeteSASTestUtil.createRequeteSAS(//instance constructor);
requeteSASJdbc2 = RequeteSASTestUtil.createRequeteSAS(//instance constructor);
}
#Test
public void testGetRequetes() {
String mockRemoteUser = "STATT-00016-0102035";
List<RequeteSAS> listReqTest = new ArrayList<RequeteSAS>();
listReqTest.add(requeteSASJdbc1);
listReqTest.add(requeteSASJdbc2);
Mockito.doReturn(listReqTest).when(mockRequeteService).readListeRequeteSas(mockRemoteUser);
Mockito.doReturn(mockRemoteUser).when((BaseController)restController).getCurrentUserLogin();
ResponseEntity<SimpleServiceResponse<List<RequeteSAS>>> repReq = restController.readListeRequeteSas();
List<RequeteSAS> listReq = repReq.getBody().getValue();
//Assert List
}
As I said, with this, the method "getCurrentUserLogin()" from BaseController is still called, it does not return the mockRemoteUser. I've read many thread on this exact subject i.e. mocking a superclass method, I followed the answer, but still my test fails because "getCurrentUserLogin()" returns null instead of the mocked user.
Does somebody see where the problem is? Thank you.
Related
I am using JUNIT5, have been trying to fully coverage a piece of code that involves System.getenv(""); I writed a couple classes to replicate what I am experiencing right now and so you can use them to understand me also (minimal reproducible example):
First we have the service I need to get with full coverage (ServiceToTest.class) (it has a CustomContainer object which contains methods that it needs):
#Service
public class ServiceToTest {
private final CustomContainer customContainer;
public ServiceToTest() {
Object configuration = new Object();
String envWord = System.getenv("envword");
this.customContainer = new CustomContainer(configuration, envWord == null ? "default" : envWord);
}
public String getContainerName() {
return customContainer.getContainerName();
}
}
CustomContainer.class:
public class CustomContainer {
#Getter
String containerName;
Object configuration;
public CustomContainer(Object configuration, String containerName) {
this.configuration = configuration;
this.containerName = containerName;
}
}
I have tried using ReflectionTestUtils to set the envWord variable without success... I tried this https://stackoverflow.com/a/496849/12085680, also tried using #SystemStubsExtension https://stackoverflow.com/a/64892484/12085680, and finally I also tried using Spy like in this answer https://stackoverflow.com/a/31029944/12085680
But the problem is that this variable is inside the constructor so this only executes once and I think that it happens before any of this configs I tried before can apply, here is my test class:
#ExtendWith(MockitoExtension.class)
class TestService {
// I have to mock this becase in real project it has methods which I need mocked behavour
private static CustomContainer mockCustomContainer = mock(CustomContainer.class);
// The serviceToTest class in which I use ReflectionTestUtils to use the mock above
// Here is where the constructor gets called and it happens BEFORE (debuged) the setup method
// which is anotated with #BeforeAll
private static ServiceToTest serviceToTest = new ServiceToTest();
#BeforeAll
static void setup() {
// set the field customContainer at serviceToTest class to mockCustomContainer
ReflectionTestUtils.setField(serviceToTest, "customContainer", mockCustomContainer);
}
#Test
void testGetContainerNameNotNull() {
assertNull(serviceToTest.getContainerName());
}
}
I need to write a test in which serviceToTest.getContainerName is not null but the real purpose of this is to have coverage of this sentence envWord == null ? "default" : envWord so it would be a test that is capable of executing the constructor and mocking System.getenv() so that it returns not null...
Right now the coverage looks like this and I can not find a way to make it 100% Any ideas??
EDIT:
So after following tgdavies suggestion, the code can be 100% covered, so this is the way:
Interface CustomContainerFactory:
public interface CustomContainerFactory {
CustomContainer create(Object configuration, String name);
}
CustomContainerFactoryImpl:
#Service
public class CustomContainerFactoryImpl implements CustomContainerFactory {
#Override
public CustomContainer create(Object configuration, String name) {
return new CustomContainer(configuration, name);
}
}
EnvironmentAccessor Interface:
public interface EnvironmentAccessor {
String getEnv(String name);
}
EnvironmentAccessorImpl:
#Service
public class EnvironmentAccessorImpl implements EnvironmentAccessor {
#Override
public String getEnv(String name) {
return System.getenv(name);
}
}
Class ServiceToTest after refactoring:
#Service
public class ServiceToTest {
private final CustomContainer customContainer;
public ServiceToTest(EnvironmentAccessor environmentAccessor, CustomContainerFactory customContainerFactory) {
Object configuration = new Object();
String envWord = environmentAccessor.getEnv("anything");
this.customContainer = customContainerFactory.create(configuration, envWord == null ? "default" : envWord);
}
public String getContainerName() {
return customContainer.getContainerName();
}
}
Finally the test case after refactoring (here is were I think it can be improved maybe?):
#ExtendWith(MockitoExtension.class)
class TestService {
private static CustomContainer mockCustomContainer = mock(CustomContainer.class);
private static CustomContainerFactory customContainerFactoryMock = mock(CustomContainerFactoryImpl.class);
private static EnvironmentAccessor environmentAccessorMock = mock(EnvironmentAccessorImpl.class);
private static ServiceToTest serviceToTest;
#BeforeAll
static void setup() {
when(environmentAccessorMock.getEnv(anyString())).thenReturn("hi");
serviceToTest = new ServiceToTest(environmentAccessorMock, customContainerFactoryMock);
ReflectionTestUtils.setField(serviceToTest, "customContainer", mockCustomContainer);
when(serviceToTest.getContainerName()).thenReturn("hi");
}
#Test
void testGetContainerNameNotNull() {
assertNotNull(serviceToTest.getContainerName());
}
#Test
void coverNullReturnFromGetEnv() {
when(environmentAccessorMock.getEnv(anyString())).thenReturn(null);
assertAll(() -> new ServiceToTest(environmentAccessorMock, customContainerFactoryMock));
}
}
Now the coverage is 100%:
EDIT 2:
We can improve the test class and get the same 100% coverage like so:
#ExtendWith(MockitoExtension.class)
class TestService {
private static CustomContainer mockCustomContainer = mock(CustomContainer.class);
private static IContainerFactory customContainerFactoryMock = mock(ContainerFactoryImpl.class);
private static IEnvironmentAccessor environmentAccessorMock = mock(EnvironmentAccessorImpl.class);
private static ServiceToTest serviceToTest;
#BeforeAll
static void setup() {
when(environmentAccessorMock.getEnv(anyString())).thenReturn("hi");
when(customContainerFactoryMock.create(any(), anyString())).thenReturn(mockCustomContainer);
serviceToTest = new ServiceToTest(environmentAccessorMock, customContainerFactoryMock);
}
#Test
void testGetContainerNameNotNull() {
assertNotNull(serviceToTest.getContainerName());
}
#Test
void coverNullReturnFromGetEnv() {
when(environmentAccessorMock.getEnv(anyString())).thenReturn(null);
assertAll(() -> new ServiceToTest(environmentAccessorMock, customContainerFactoryMock));
}
}
Refactor your code to make it testable, by moving object creation and static method calls to components, which you can mock in your tests:
interface ContainerFactory {
CustomContainer create(Object configuration, String name);
}
interface EnvironmentAccessor {
String getEnv(String name);
}
#Service
public class ServiceToTest {
private final CustomContainer customContainer;
public ServiceToTest(ContainerFactory containerFactory, EnvironmentAccessor environmentAccessor) {
Object configuration = new Object();
String envWord = environmentAccessor.getEnv("envword");
this.customContainer = containerFactory.create(configuration, envWord == null ? "default" : envWord);
}
public String getContainerName() {
return customContainer.getContainerName();
}
}
So, currently, I'm testing on a Service class
This is my ConvertService.java
#Service
public class ConvertService {
private final NetworkClient networkClient; //NetworkClient is a Service too
private final ConvertUtility convertUtility;
public ConvertService(Network networkClient) {
convertUtility = ConvertFactory.of("dev", "F");
this.networkClient = networkClient
}
public Response convert(Request request) {
User user = networkClient.getData(request.getId()); //User is POJO class
Context context = convertUtility.transform(request.getToken()) //getToken returns a String
//Context is a normal Java
}
}
This is my ConvertServiceTest.java
#SpringBootTest
#RunWith(MockitoJunitRunner.class)
class ConvertServiceTest {
#MockBean
private NetworkClient networkClient;
#Mock
ConvertUtility convertUtility;
private ConvertService convertService;
#BeforeEach
void setUp() {
convertService = new ConvertService(networkClient);
}
private mockMethod() {
Request request = Request(1000);
User user = new User("user1");
Context context = new Context();
when(networkClient.getData(anyLong())).thenReturn(user);
when(convertUtility.transform(any(String.class)).thenReturn(context);
Response response = convertService.convert(request); //it throws me an exception here
}
}
convertService.convert(request); throws an exception
pointing inside convertUtility.transform(request.getToken())
I'm not sure why it's processing everything from transform method, when I wrote
when(convertUtility.transform(any(String.class)).thenReturn(context);
Can anyone please help?
EDIT: ConvertUtility is a read-only library
Inside your public constructor, you're using a static factory method to get an instance of the ConvertUtility. You'd have to mock the static ConvertUtility.of() method to work with a mock during your test.
While Mockito is able to mock static methods, I'd recommend refactoring (if possible) your class design and accepting an instance of ConvertUtility as part of the public constructor:
#Service
public class ConvertService {
private final NetworkClient networkClient; //NetworkClient is a Service too
private final ConvertUtility convertUtility;
public ConvertService(Network networkClient, ConvertUtility convertUtility) {
this.convertUtility = convertUtility
this.networkClient = networkClient
}
}
With this change, you can easily mock the collaborators of your ConvertService when writing unit tests:
#ExtendWith(MockitoExtension.class)
class ConvertServiceTest {
#Mock
private NetworkClient networkClient;
#Mock
private ConvertUtility convertUtility;
#InjectMocks
private ConvertService convertService;
#Test // make sure it's from org.junit.jupiter.api
void yourTest() {
}
}
Below is main code consist of one util class and service class using it
#PropertySource("classpath:atlas-application.properties")
public class ApacheAtlasUtils {
#Value("${atlas.rest.address}")
private String atlasURL;
#Value("${atlas.rest.user}")
private String atlasUsername;
#Value("${atlas.rest.password}")
private String atlasPassword;
private AtlasClientV2 client;
public AtlasClientV2 createClient() {
if (client == null) {
return new AtlasClientV2(new String[] {atlasURL}, new String[] {atlasUsername, atlasPassword});
} else {
return client;
}
}
}
Service Class is below :-
#Override
public Page<SearchResultDto> findFilesWithPages(QueryParent queryParent, Pageable pageable)
throws AtlasServiceException {
// Some code
client = new ApacheAtlasUtils().createClient();
//some code
}
I am writing unit test for service method and I am getting exception for createClient method asking for values for url, username and password which should not happen as this should be mocked but the mocking is giving me below error
java.lang.IllegalArgumentException: Base URL cannot be null or empty.
at com.google.common.base.Preconditions.checkArgument(Preconditions.java:141)
at org.apache.atlas.AtlasServerEnsemble.<init>(AtlasServerEnsemble.java:35)
at org.apache.atlas.AtlasBaseClient.determineActiveServiceURL(AtlasBaseClient.java:318)
at org.apache.atlas.AtlasBaseClient.initializeState(AtlasBaseClient.java:460)
at org.apache.atlas.AtlasBaseClient.initializeState(AtlasBaseClient.java:448)
at org.apache.atlas.AtlasBaseClient.<init>(AtlasBaseClient.java:132)
at org.apache.atlas.AtlasClientV2.<init>(AtlasClientV2.java:82)
at com.jlr.stratus.commons.utils.ApacheAtlasUtils.createClient(ApacheAtlasUtils.java:40)
at com.jlr.stratus.rest.service.impl.FileSearchService.findFilesWithPages(FileSearchService.java:49)
The Test code is as follows:-
private FileSearchService fileSearchService;
#Spy
private ApacheAtlasUtils apacheAtlasUtils;
#Mock
private AtlasClientV2 client;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
fileSearchService = new FileSearchService();
}
#Test
public void findFilesWithPages_searchAll() throws AtlasServiceException {
Mockito.doReturn(client).when(apacheAtlasUtils).createClient();
service.search(queryParent,pageable);
}
Your idea with spying is adequate (you can even go for mocking if you do not actually need any true implementation of that class).
The problem lies in the implementation:
// Some code
client = new ApacheAtlasUtils().createClient();
//some code
}
Instead of having the ApacheAtlasUtils as an instance variable (or a supplier method) you create the instance on the fly.
Mockito is not smart enough to catch that operation and replace the real object with you spy.
With the supplier method you can set up your test as follows:
#Spy
private FileSearchService fileSearchService = new FileSearchService();
#Spy
private ApacheAtlasUtils apacheAtlasUtils = new ApacheAtlasUtils();
#Mock
private AtlasClientV2 client;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
doReturn(apacheAtlasUtils).when(fileSearchService).getApacheUtils();
}
in your SUT:
#Override
public Page<SearchResultDto> findFilesWithPages(QueryParent queryParent, Pageable pageable)
throws AtlasServiceException {
// Some code
client = getApacheUtils().createClient();
//some code
}
ApacheAtlasUtils getApacheUtils(){
return new ApacheAtlasUtils();
}
I have this method and it does return a list:
public List<ReportReconciliationEntry> getMissingReports(List<ReportReconciliationEntry> expectedReports,
List<GeneratedReportContent> generatedReports){
...
return missingReports;
}
but this method is never called:
#AfterReturning(value = "execution(* com.XXX.YYY.ZZZ.service.ReconciliationService.getMissingReports(..)) && args(expectedReports,generatedReports)", argNames = "expectedReports,generatedReports,missingReports", returning = "missingReports")
public void logReportReconciliationException(List<ReportReconciliationEntry> expectedReports, List<GeneratedReportContent> generatedReports, List<ReportReconciliationEntry> missingReports) {
final String notApplicable = properties.getNotApplicable();
ReportingAlertMarker marker = ReportingAlertMarker.builder()
.eventType(E90217)
.userIdentity(notApplicable)
.destinationIp(properties.getDestinationIp())
.destinationPort(properties.getDestinationPort())
.dataIdentity(notApplicable)
.resourceIdentity(notApplicable)
.responseCode(404)
.build();
MDC.put(SYSTEM_COMPONENT, properties.getBpsReportGenerationService());
System.out.println(missingReports);
logWrapper.logError(marker, "SDGFHDZFHDFR!!");
}
I check the return of the first method with a breakpoint. It does return a list, but the #AfterReturning is never called, although the IDE shows the "Navigate to AOP advices" icon. What am I missing?
This is what my class looks like:
#Component
#Aspect
#Slf4j
public class ReportingAlertAspect {
private final LogWrapper logWrapper;
private final ReportingAlertProperties properties;
public ReportingAlertAspect(final ReportingAlertProperties properties, final LogWrapper logWrapper) {
this.logWrapper = logWrapper;
this.properties = properties;
}
....
}
I have another class with a function in it and this one works fine:
#Component
#Aspect
#Slf4j
public class ReportingInfoAspect {
private final LogWrapper logWrapper;
private final ReportingAlertProperties properties;
#AfterReturning(value = "execution(* com.xxx.yyy.zzz.qqq.ReconciliationService.reconcile(..)) && args(windowId)", argNames = "windowId,check",
returning = "check")
public void logSuccessfulReportReconciliation(ReconciliationEvent windowId, boolean check){
String notApplicable = properties.getNotApplicable();
MDC.put(SYSTEM_COMPONENT, properties.getBpsReportGenerationService());
ReportingAlertMarker marker = ReportingAlertMarker.builder()
.eventType(E90293)
.userIdentity(notApplicable)
.destinationIp(properties.getDestinationIp())
.destinationPort(properties.getDestinationPort())
.dataIdentity(notApplicable)
.resourceIdentity(notApplicable)
.responseCode(200)
.build();
if (check){
logWrapper.logInfo(marker, "All reports for windowId {} were generated successfully", windowId.windowId);
}
}
}
I found the problem.
The getMissingReports method was called from another method inside the same class. This is a case of self-invocation and the method was never called through the proxy.
This is what the class looks like:
#Service
#RequiredArgsConstructor
public class ReconciliationService {
private final ReconciliationRepository reconciliationRepository;
private final ReportSafeStoreClientService reportSafeStoreClientService;
#Handler
public whatever whatever() {
...
getMissingReports()
}
}
You can find more info here
I have a little problem. I think this is typical question. However, I can't find good example. My application is using Jersey. And I want to test controller by client as test. Controller has private field - StudentService. When I debug test I see, that field is null. This leads to error. And I need to inject this field. I tried this:
My Controller
#Path("/student")
#Component
public class StudentResourse {
#Autowired
private StrudentService service; // this field Spring does not set
#Path("/getStudent/{id}")
#GET
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Student getStudent(#PathParam("id") long id) {
return service.get(id);
}
}
My JUnit test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:config.xml")
#TestExecutionListeners({ DbUnitTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class })
public class StudentResourseTest extends JerseyTest {
private static final String PACKAGE_NAME = "com.example.servlet";
private static final String FILE_DATASET = "/data.xml";
#Autowired
private StudentService service; // this field is setted by Spring, but I do not need this field for test
public StudentResourseTest() {
super(new WebAppDescriptor.Builder(PACKAGE_NAME).build());
}
#Override
protected TestContainerFactory getTestContainerFactory() {
return new HTTPContainerFactory();
}
#Override
protected AppDescriptor configure() {
return new WebAppDescriptor.Builder("restful.server.resource")
.contextParam("contextConfigLocation",
"classpath:/config.xml").contextPath("/")
.servletClass(SpringServlet.class)
.contextListenerClass(ContextLoaderListener.class)
.requestListenerClass(RequestContextListener.class).build();
}
#Test
#DatabaseSetup(FILE_DATASET)
public void test() throws UnsupportedEncodingException {
ClientResponse response = resource().path("student").path("getStudent")
.path("100500").accept(MediaType.APPLICATION_XML)
.get(ClientResponse.class);
Student student = (Student) response.getEntity(Student.class);
} }
I guees, that problem is in test class. Because, when I run my application not in test, I can directly request students and everything working fine. But when I test classes, internal field of Controller does not setted. How to fix this bug? Thanks for your answers.
This is in my config.xml
<context:component-scan base-package="com.example" />
<bean id="StudentResourse" class="com.example.servlet.StudentResourse">
<property name="service" ref="studentService" />
</bean>
<bean id="service" class="com.example.service.StudentServiceImpl" />
One issue may be that you're trying to configure your test application in constructor and in configure() method. Use one or another but not both because in this case your configure() method is not invoked and hence you may not be using SpringServlet and everything that is defined in this method.
Reference: https://github.com/jiunjiunma/spring-jersey-test and http://geek.riffpie.com/unit-testing-restful-jersey-services-glued-together-with-spring/
Idea is to get a hold of the application context inside jersey by using ApplicationContextAware interface. There after we can grab the exact bean already created by spring, in your case, StudentService. Below example shows a mocked version of the dependency, SampleService, used to test the resource layer apis.
Resource class delegating the processing to a service layer
#Component
#Path("/sample")
public class SampleResource {
#Autowired
private SampleService sampleService;
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path ("/{id}")
public Sample getSample(#PathParam("id") int id) {
Sample sample = sampleService.getSample(id);
if (sample == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return sample;
}
}
Service layer encapsulating business logic
#Service
public class SampleService {
private static final Map<Integer, Sample> samples = new HashMap<>();
static {
samples.put(1, new Sample(1, "sample1"));
samples.put(2, new Sample(2, "sample2"));
}
public Sample getSample(int id) {
return samples.get(id);
}
}
Unit test for the above resource
public class SampleResourceTest extends SpringContextAwareJerseyTest {
private SampleService mockSampleService;
// create mock object for our test
#Bean
static public SampleService sampleService() {
return Mockito.mock(SampleService.class);
}
/**
* Create our own resource here so only the test resource is loaded. If
* we use #ComponentScan, the whole package will be scanned and more
* resources may be loaded (which is usually NOT what we want in a test).
*/
#Bean
static public SampleResource sampleResource() {
return new SampleResource();
}
// get the mock objects from the internal servlet context, because
// the app context may get recreated for each test so we have to set
// it before each run
#Before
public void setupMocks() {
mockSampleService = getContext().getBean(SampleService.class);
}
#Test
public void testMock() {
Assert.assertNotNull(mockSampleService);
}
#Test
public void testGetSample() {
// see how the mock object hijack the sample service, now id 3 is valid
Sample sample3 = new Sample(3, "sample3");
Mockito.when(mockSampleService.getSample(3)).thenReturn(sample3);
expect().statusCode(200).get(SERVLET_PATH + "/sample/3");
String jsonStr = get(SERVLET_PATH + "/sample/3").asString();
Assert.assertNotNull(jsonStr);
}
}
SpringContextAwareJerseyTest
#Configuration
public class SpringContextAwareJerseyTest extends JerseyTest {
protected static String SERVLET_PATH = "/api";
final private static ThreadLocal<ApplicationContext> context =
new ThreadLocal<>();
protected String getResourceLocation() {
return "example.rest";
}
protected String getContextConfigLocation() {
return getClass().getName();
}
static private String getContextHolderConfigLocation() {
return SpringContextAwareJerseyTest.class.getName();
}
protected WebAppDescriptor configure() {
String contextConfigLocation = getContextConfigLocation() + " " +
getContextHolderConfigLocation();
Map<String, String> initParams = new HashMap<>();
initParams.put("com.sun.jersey.config.property.packages",
getResourceLocation());
initParams.put("com.sun.jersey.api.json.POJOMappingFeature", "true");
return new WebAppDescriptor.Builder(initParams)
.servletClass(SpringServlet.class)
.contextParam(
"contextClass",
"org.springframework.web.context.support.AnnotationConfigWebApplicationContext")
.contextParam("contextConfigLocation", contextConfigLocation)
.servletPath(SERVLET_PATH) // if not specified, it set to root resource
.contextListenerClass(ContextLoaderListener.class)
.requestListenerClass(RequestContextListener.class)
.build();
}
protected final ApplicationContext getContext() {
return context.get();
}
#Bean
public static ContextHolder contextHolder() {
return new ContextHolder();
}
private static class ContextHolder implements ApplicationContextAware {
#Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
context.set(applicationContext);
}
}
}
Using the above with jersey 1.8