April 1, 2020
Estimated Post Reading Time ~

How to write Unit Tests in AEM

Use Case: Writing tests for AEM application.

Current Issue: As your project and codebase grow, it is really important to make sure that test coverage for code is there to maintain consistency and sanity of your code. Writing test cases for AEM is a little bit different than writing conventional Java test cases, This makes it difficult for beginners to write test cases for AEM application.

The idea of this post to give different options available for writing unit tests for AEM services.

Prerequisite:
Familiar with Java Junit test framework http://junit.org
Familiar with Java Mockito test framework http://site.mockito.org/
Optional: PowerMockito test framework http://powermock.github.io/Good to know:

Sling test framework (Mock and IT) https://sling.apache.org/documentation/development.html
AEM Mock http://wcm.io/testing/aem-mock/ and http://wcm.io/testing/aem-mock/apidocs/
WCMUse classes Mocking http://aempodcast.com/2015/testing/unit-testing-wcmuse-classes-using-mockito/#.WEmiUMMrJKcI would explain how you can have better test coverage for your application by giving different use cases,

Dependencies: It is recommended to have following dependencies into your pom before start writing for tests for your application

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.8.2</version>
    <scope>test</scope>
</dependency>


<dependency>
    
    <groupId>org.slf4j</groupId>    
    <artifactId>slf4j-simple</artifactId>    
    <version>1.5.11</version>    
    <scope>test</scope>
</dependency>


<dependency>
    
    <groupId>junit-addons</groupId>    
    <artifactId>junit-addons</artifactId>    
    <version>1.4</version>    
    <scope>test</scope>
</dependency>


<!-- Custom WCM Mock for AEM6 -->
<dependency>
    
    <groupId>io.wcm</groupId>    
    <artifactId>io.wcm.testing.aem-mock</artifactId>    
    <version>1.3.0</version>
</dependency>


<!-- Sling Mocks -->
<dependency>    
    <groupId>org.apache.sling</groupId>              
    <artifactId>org.apache.sling.testing.resourceresolver-mock</artifactId>
    <version>1.1.4</version>
</dependency>

<dependency>
    
    <groupId>org.apache.sling</groupId>    
    <artifactId>org.apache.sling.testing.osgi-mock</artifactId>  
    <version>1.2.0</version>
</dependency>

<dependency>
    
    <groupId>org.apache.sling</groupId>          
    <artifactId>org.apache.sling.testing.sling-mock</artifactId>          
    <version>1.2.0</version>
</dependency>

<dependency>
    
    <groupId>org.apache.sling</groupId>  
    <artifactId>org.apache.sling.testing.jcr-mock</artifactId> 
    <version>1.1.4</version>
</dependency>

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>1.10.19</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.10.19</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>1.6.2</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito</artifactId>
    <version>1.6.2</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>com.adobe.aem</groupId>
    <artifactId>uber-jar</artifactId>
    <version>6.1.2</version>
    <classifier>apis</classifier>
    <scope>provided</scope>
</dependency>


This is the simplest use case where your generic helper class (For example StringUtils, DateUtils) is not using any AEM libraries. For this, you can simply use Junit to write your unit test. https://www.tutorialspoint.com/junit

Here is very simple example:

package com.wemblog.foundation.utility;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

public class CommonStringUtilTest {

@Test
public void testIsEmpty() {
assertTrue(CommonStringUtil.isEmpty(""));
assertFalse(CommonStringUtil.isEmpty(" "));
assertFalse(CommonStringUtil.isEmpty("test"));
assertFalse(CommonStringUtil.isEmpty(" test "));
assertTrue(CommonStringUtil.isEmpty(null));
}

@Test
public void testIsEmptyWithTrim() {
assertTrue(CommonStringUtil.isEmpty("", false));
assertTrue(CommonStringUtil.isEmpty(" ", true));
assertFalse(CommonStringUtil.isEmpty(" ", false));
assertFalse(CommonStringUtil.isEmpty("test", true));
assertFalse(CommonStringUtil.isEmpty(" test ", true));
assertTrue(CommonStringUtil.isEmpty(null, true));
assertTrue(CommonStringUtil.isEmpty(null, false));
}

@Test
public void testConvertArrayToString() {
String[] test = { "test1", "test2" };
assertEquals(CommonStringUtil.convertArrayToString(new String[1]), "");
assertEquals(CommonStringUtil.convertArrayToString(null), "");
assertEquals(CommonStringUtil.convertArrayToString(test), "test1,test2");
test[1] = "test3 test4";
assertEquals(CommonStringUtil.convertArrayToString(test), "test1,test3 test4");
ArrayList<String> test1 = new ArrayList<String>();
test1.add("test1 test2");
test1.add("test3");
assertEquals(CommonStringUtil.convertArrayToString(test1.toArray(new String[test1.size()])),
"test1 test2,test3");
}

@Test
public void testConvertArrayListToString() {
List<String> test = new ArrayList<String>();
assertEquals(CommonStringUtil.convertArrayListToString(null), "");
assertEquals(CommonStringUtil.convertArrayListToString(test), "");
test.add("test1");
test.add("test2");
assertEquals(CommonStringUtil.convertArrayListToString(test), "test1,test2");
test.add("test3 test4");
assertEquals(CommonStringUtil.convertArrayListToString(test), "test1,test2,test3 test4");
}

}


Case 2: Writing test cases for AEM Helper class
This is the second use case where you want to test AEM helper methods. For this, you can use a combination of Junit and Mockito. Use Mockito to Mock AEM services and methods and Junit for the assertion.

Here is a simple example

package com.wemblog.util;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import javax.servlet.http.Cookie;
import javax.servlet.jsp.PageContext;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.NonExistingResource;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import com.day.cq.commons.Externalizer;

@RunWith(MockitoJUnitRunner.class)
public class LiCommonRequestUtilsTest {

@Mock
SlingHttpServletRequest _httpServletRequest;

@Mock
ResourceResolver _resourceResolver;

@Mock
Resource _resource;

@Mock
Cookie _bCookie;

@Mock
Externalizer _externalizer;

@Before
public void setUp() {
when(_httpServletRequest.getServerName()).thenReturn("www.test.com");
when(_httpServletRequest.getRequestURI()).thenReturn("/something/andsomething");

}


/**
* 1. When there is Host header 2. No Host Header 3. Host Header Null 4. Host Header Empty
*
*/
@Test
public void getServerNameTest() {
assertNull(LiCommonRequestUtils.getServerName(null));
assertEquals(LiCommonRequestUtils.getServerName(_httpServletRequest), "www.test.com");
when(_httpServletRequest.getHeader("Host")).thenReturn("www.test2.com");
assertEquals(LiCommonRequestUtils.getServerName(_httpServletRequest), "www.test2.com");
when(_httpServletRequest.getHeader("Host")).thenReturn("");
assertEquals(LiCommonRequestUtils.getServerName(_httpServletRequest), "www.test.com");
when(_httpServletRequest.getHeader("Host")).thenReturn(null);
assertEquals(LiCommonRequestUtils.getServerName(_httpServletRequest), "www.test.com");
}


/**
* 1. Null test 2. With HTTP header only 3. With HTTP header and Netscaler header 4. With https header 5. With https
* and null Netscaler header
*/
@Test
public void isHttpOrHttpsRequestTest() {
// For null input
assertFalse(LiCommonRequestUtils.isHTTPRequest(null));
assertFalse(LiCommonRequestUtils.isHTTPSRequest(null));
.thenReturn(LiCommonRequestUtils.HTTPS_PROTOCOL);
assertFalse(LiCommonRequestUtils.isHTTPRequest(_httpServletRequest));
assertTrue(LiCommonRequestUtils.isHTTPSRequest(_httpServletRequest));

when(_httpServletRequest.getProtocol()).thenReturn("HTTP/1.1");
assertTrue(LiCommonRequestUtils.isHTTPRequest(_httpServletRequest));
assertFalse(LiCommonRequestUtils.isHTTPSRequest(_httpServletRequest));
// If it is https request
when(_httpServletRequest.getProtocol()).thenReturn("HTTPS/1.1");
assertFalse(LiCommonRequestUtils.isHTTPRequest(_httpServletRequest));
assertTrue(LiCommonRequestUtils.isHTTPSRequest(_httpServletRequest));

}


@Test
public void getCompleteResourcePathTest() {
// Null check
assertNull(LiCommonRequestUtils.getCompleteResourcePath(null));
// Exact Path check
when(_httpServletRequest.getRequestURI()).thenReturn("/content/test/test");
when(_resource.getPath()).thenReturn("/content/test/test");
when(_resourceResolver.resolve("/content/test/test")).thenReturn(_resource);
when(_httpServletRequest.getResourceResolver()).thenReturn(_resourceResolver);
assertEquals(LiCommonRequestUtils.getCompleteResourcePath(_httpServletRequest), "/content/test/test");
// Vanity Path check
when(_httpServletRequest.getRequestURI()).thenReturn("/vanity/test/test");
when(_resourceResolver.resolve("/vanity/test/test")).thenReturn(_resource);
assertEquals(LiCommonRequestUtils.getCompleteResourcePath(_httpServletRequest), "/content/test/test");
// Non existance Resource check
when(_resourceResolver.resolve("/vanity/test/test")).thenReturn(new NonExistingResource(_resourceResolver, null));
assertNull(LiCommonRequestUtils.getCompleteResourcePath(_httpServletRequest));
}


/**
* 1. True 2. True 3. False 4. False 5. False 6. False 7. False
*/

@Test
public void isValidIPAddressTest() {
// true for valid IPV4
assertTrue(LiCommonRequestUtils.isValidIPAddress("199.101.162.221"));

// true for valid IPV6
assertTrue(LiCommonRequestUtils.isValidIPAddress("2620:109:c00d:100::c765:a381"));

// false if IP is null
assertFalse(LiCommonRequestUtils.isValidIPAddress(null));

// false if ip is blank
assertFalse(LiCommonRequestUtils.isValidIPAddress(""));

// false if its a random string
assertFalse(LiCommonRequestUtils.isValidIPAddress("1.3.1.1.1.1.1.1.1"));

// false if its a random string
assertFalse(LiCommonRequestUtils.isValidIPAddress("abc.f.ve.c"));

// false if its a random string
assertFalse(LiCommonRequestUtils.isValidIPAddress("test"));

}

/**
* 1. True 2. True 3. False 4. False 5. False 6. False 7. False 8. False
*/
@Test
public void isIPV6AddressTest() {

// true for valid compressed IPV6
assertTrue(LiCommonRequestUtils.isIPV6Address("2620:109:c00d:100::c765:a381"));

// true for valid standard IPV6
assertTrue(LiCommonRequestUtils.isIPV6Address("3ffe:1900:4545:3:200:f8ff:fe21:67cf"));

// false if IP is null
assertFalse(LiCommonRequestUtils.isIPV6Address(null));

// false if ip is blank
assertFalse(LiCommonRequestUtils.isIPV6Address(""));

// false if its a random string
assertFalse(LiCommonRequestUtils.isIPV6Address("1.3.1.1.1.1.1.1.1"));

// false if its a random string
assertFalse(LiCommonRequestUtils.isIPV6Address("abc.f.ve.c"));

// false if its a random string
assertFalse(LiCommonRequestUtils.isIPV6Address("test"));

// true for valid IPV4
assertFalse(LiCommonRequestUtils.isIPV6Address("199.101.162.221"));

}

}


Case 3: Writing test cases for AEM services
Now it gets a little bit tricky where you need to mock certain behavior of bundle and implicit object. That's why Sling has created a Mock version of sling objects and wcm.io has created a mock version of AEM objects. You can just use AEM mock http://wcm.io/testing/aem-mock/ to achieve most of your use cases. (AEM mock extend Sling mock).

here are some of the common use cases you will come across while testing your service.

1) How can I mock content my service is running against?
For this, it is recommended to use content loader API http://wcm.io/testing/aem-mock/usage.html to either load existing JSON based resource (You can simply get it by creating a resource in CRXDE and then using something like RESOURCEPATH.infinity.json to get JSON for that resource) or just create a mock resource using ContentBuilder context.create().resource() or ResourceBuilder context.build().resource() http://wcm.io/testing/aem-mock/apidocs/

Note that if you are mocking Page object then you have to use AEM mock using aemcontext.pageManager().create()

2) How Can I initialize properties in the bundle?
You can use register and activate OSGI service with properties http://wcm.io/testing/aem-mock/usage.html#Registering_OSGi_service for that. Here is some example

//Method 1, By using Inject Mock

@RunWith(PowerMockRunner.class)
@PrepareForTest(MyServiceImpl.class)
public class MyServiceImplTest {

@InjectMocks
MyServiceImpl _myService;

@Before
public void setUp() throws Exception {
_config = new HashMap<Object, Object>();
_config.put("test1", "test1");
_config.put("test2", "test3");
}

@Test
public void someTest() {
myService.activate(_config);
assertEquals("test", something.yourMethod());
}

}



//Method 2 using AEM context

public class MyServiceImplTest {

MyServiceImpl _myService;

@Rule
public final AemContext aemContext = new AemContext();

@Before
public void setUp() throws Exception {
_config = new HashMap<Object, Object>();
_config.put("test1", "test1");
_config.put("test2", "test3");
_myService = aemContext.registerInjectActivateService(new MyServiceImpl(), _config);
}

@Test
public void someTest() {
assertEquals("test", _myService.yourMethod());
}

}


3) How Can I inject another service for my service?
You can either Mock service or use register service API for that http://wcm.io/testing/aem-mock/usage.html#Registering_OSGi_service

Note that when you inject a service to your service using Reference then you have to register this your injected service, otherwise your test will fail.

4) How Can I test the sling model?
You can use the AEM context for that. http://wcm.io/testing/aem-mock/usage.html#Sling_Models

Case 4: Writing test cases for AEM servlets and filters

This is very similar to how you would do test cases for Service. For request and response, you either have to mock request/response object using Mockito or Use Spy or use sling request and response mock. Since a lot of methods in filter and servlet do not return any result, Make Mockito verify your friend. Here is one example using simple mockito to test servlet

package com.wemblog.test;

import com.adobe.granite.xss.XSSAPI;
import com.google.gson.JsonArray;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

import static org.junit.Assert.*;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.doAnswer;

import io.wcm.testing.mock.aem.junit.AemContext;
import org.apache.sling.testing.mock.sling.ResourceResolverType;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.Rule;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class AEMServletTest {
private String answerFromDoGet = "";
private int statusForDoGet = 0;
private JsonParser parser = new JsonParser();

@Rule
public final AemContext _context = new AemContext(ResourceResolverType.RESOURCERESOLVER_MOCK);

@Mock
private AEMTreatmentService _aemTreatServe = mock(AEMTreatmentService.class);
SlingHttpServletRequest _httpServletRequest = mock(SlingHttpServletRequest.class);
SlingHttpServletResponse _httpServletResponse = mock(SlingHttpServletResponse.class);
XSSAPI _xssapi = mock(XSSAPI.class);
PrintWriter _respOut = mock(PrintWriter.class);

@InjectMocks
AEMTreatmentServlet _aemServlet;

@Before
public void setUp() {
_context.registerService(AEMTreatmentService.class, _aemTreatServe);

try {
when(_httpServletResponse.getWriter()).thenReturn(_respOut);

doAnswer(new Answer<Object>() {
public Object answer(InvocationOnMock invocation) throws IOException {
Object[] args = invocation.getArguments();

answerFromDoGet = (String) args[0];
return null;
}
}).when(_respOut).write(any(String.class));
} catch (Exception e) {

}
}


@Test
public void testDoGet() {
try {
when(_httpServletRequest.getParameter(AEMTreatmentService.TREATMENT_PARAM)).thenReturn("foo");

_aemServlet.doGet(_httpServletRequest, _httpServletResponse);

JsonObject result = parser.parse(answerFromDoGet).getAsJsonObject();

assertEquals("correctly set status", SlingHttpServletResponse.SC_OK,
result.get(AEMTreatmentService.STATUS_KEY).getAsInt());
} catch (Exception e) {
assertTrue("error running testDoGet: " + e.getMessage(), false);
}
}

/**
* Test that the servlet returns the correct error state when the user fails
* To provide a testKey parameter.
*/
@Test
public void testDoGetNoTestKey() {
try {
when(_httpServletRequest.getParameter(AEMTreatmentService.TEST_PARAM)).thenReturn("");

_aemServlet.doGet(_httpServletRequest, _httpServletResponse);

JsonObject result = parser.parse(answerFromDoGet).getAsJsonObject();

assertEquals("correctly set status", SlingHttpServletResponse.SC_BAD_REQUEST,
result.get(AEMTreatmentService.STATUS_KEY).getAsInt());
} catch (Exception e) {
assertTrue("error running testDoGet: " + e.getMessage(), false);
}
}


@Test
public void testDoGetQAMode() {
try {
when(_httpServletRequest.getParameter(AEMTreatmentService.QA_MODE_PARAM)).thenReturn("true");

_aemServlet.doGet(_httpServletRequest, _httpServletResponse);

JsonObject result = parser.parse(answerFromDoGet).getAsJsonObject();

assertEquals("correctly set QA override", true, result.get(AEMTreatmentService.QA_MODE_KEY).getAsBoolean());
} catch (Exception e) {
assertTrue("error running testDoGet: " + e.getMessage(), false);
}
}
}

How can I measure my test coverage?
You can use jococo test coverage plugin along with your build system to measure this coverage. You can have the following plugin into your parent pom

<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.2.201409121644</version>
<executions>

<!-- Prepares the property pointing to the JaCoCo runtime agent
which is passed as VM argument when Maven the Surefire plugin is executed-->
<execution>
<id>pre-unit-test</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>

<!-- Sets the name of the property containing the settings for JaCoCo runtime agent. -->
<propertyName>surefireArgLine</propertyName>
</configuration>
</execution>


<!-- Ensures that the code coverage report for unit tests is created
after unit tests have been run. -->
<execution>
<id>post-unit-test</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<!-- Sets the output directory for the code coverage report. -->
<outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>
</configuration>
</execution>


<execution>
<id>unit-test-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.90</minimum>
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.90</minimum>
</limit>
<limit>
<counter>METHOD</counter>
<value>COVEREDRATIO</value>
<minimum>0.90</minimum>
</limit>
<limit>
<counter>CLASS</counter>
<value>COVEREDRATIO</value>
<minimum>0.90</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>


How Can I write an Integration test in AEM?
A very good example here https://github.com/sneakybeaky/AEM-Integration-Test-Example

It is based on the sling test base https://sling.apache.org/documentation/development/sling-testing-tools.html


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.