Expand my Community achievements bar.

Applications for the 2024-2025 Adobe Experience Manager Champion Program are open!

5 Techniques to Modernize Your AEMCS Tests | AEM Community Blog Seeding





5 Techniques to Modernize Your AEMCS Tests by Daniel Klco


#1 - Switch to OSGi Constructor Activation

OSGi R7 adds support for injecting referencces into a component's constructor. Why does this matter for tests?

In order to mock dependencies when using a @Reference annotation on a field, you must either:

Set a more permissive scope on the field - Exposing fields through overly permissive field restrictions breaks the encapsulation of your code
Use reflection to set the field - Reflection makes for more complex and fragile tests and makes code refactoring harder as you don't get compile-time exceptions when the code changes

Neither option is great, but instead with a constructor references, all of the services used by a component are injected when constructing the component. Let's see it in action! First, an example using field @Reference annotations:

public class BeforeComponent {

private ResourceResolverFactory resolverFactory;

[... your code here...]

class BeforeComponentTest {
void testResourceResolverFactory() throws Exception {
BeforeComponentTest testObj = new BeforeComponentTest();
Field rrf = testObj.getClass().getDeclaredField("resolverFactory");
rrf.set(testObj, mockResourceResolverFactory);
[...test code here...]

And the same class updated to use constructor references:

public class AfterComponent {

private final ResourceResolverFactory resolverFactory;

public AfterComponent(@Reference ResourceResolverFactory resolverFactory) {
this.resolverFactory = resolverFactory;

[... your code here...]

class AfterComponentTest {
void testResourceResolverFactory() throws Exception {
AfterComponentTest testObj = new AfterComponentTest(mockResourceResolverFactory);
[...test code here...]
#2 - Upgrade to JUnit 5

Who wants to spend a bunch of time rewriting tests!?! Me neither, however JUnit 5 both great new features and an easy upgrade process via the junit-vintage-engine. This allows you to run your existing JUnit 3/4 tests alongside the new JUnit 5 Jupiter tests.

So what's the big deal about JUnit 5? In my opinion, there are two features that make JUnit 5 a huge productivity upgrade from JUnit 4.

Parametrized Tests

JUnit 4 has support for Parametrized Tests, but it's a pretty clunky process. With JUnit 5 you can easily implement common scenarios such as testing input validation with just annotations. For example if I wanted to test a node name validator, I could write a test like the following:

@ValueSource(strings = {"ns:ns2:id", "name/other"})
void rejectsInvalidNames(String name) {

This just scratches the surface of what you can do with Parameterized Tests, but hopefully it gives you an idea of the capabilities of this feature.

Lambda Support

JUnit 5 adds Lambda support for Assertions, Assumptions and other testing features. This allows for some really neat features such as grouping assertions and asserting against exceptions within a test (rather than being the entire scope of the test).

void groupedAssertions() {
assertAll("can read",
() -> assertEquals("/content/dam", resource.getPath()),
() -> assertEquals("AEM Assets", resource.getValueMap().get("jcr:content/jcr:title","")

void exceptionTesting() {
Exception exception = assertThrows(RepositoryException.class, () ->
assertTrue("SOME MESSAGE HERE", exception.getMessage());

#3 - Use Mockito for Simple Mocking

AEM is a complex beast. While you can certainly try to isolate your code as much as possible, there are still interactions with AEM that you'll need to test. Mockito enables you to mock services and application state without requiring instantiating the full dependency tree.

class SimpleMockedTest {
void testMockedObject() {
Resource myTestResource = mock(Resource.class);
System.out.println(myTestResource.getResourceType()); // will print test/type

Mockito's power really shines with answers and verifiers. While you may have mocks which can simply always return a value, with Mockito you can also verify that your mock's methods have been called, assert values passed to mocked methods or dynamically call code based on a mocked method execution.

class ComplexMockedTest {
void testMockedObject() {
ValueMap myValueMap = mock(ValueMap.class);
Map<String,String> values = Map.of("hello", "world");
when(myValueMap.get(anyString(), anyString())).thenAnswer(inv -> values.get(inv.get(0));

verify(myValueMap.get(anyString(), anyString()));

#4 - Use Sling / AEM Mocks to Mock the Repository

Mocking has diminishing returns, especially for services where you don't own the contract or the contract isn't fixed. In these cases, bringing in Sling or AEM Mocks can vastly simplify the process of setting up a mocked environment and be a powerful tool for testing against a repository state.

Sling and AEM Mocks come loaded with the basic services you need as well as an empty mocked repository for loading content, you just need to add any custom (or non-standard) services and load the required content in your test setup:

public class ExampleTest {

private final SlingContext context = new SlingContext();

public void beforeEach(){
// initialize state

public void testSomething() {
Resource resource = context.resourceResolver().getResource("/content/sample/en");
// further testing


#5 - Reduce IT Flakiness with Awaitility

Integration tests in a CMS like AEM are... complex. Since AEM will renders markup based on the content provided to your code, writing ITs is challenging as you need to account for the markup variability.

AEM As a Cloud Service brings a bit more complexity due to it's cloud scalability. Due to the constraints of the CAP theorem, AEM as a Cloud Service trades Consistency for Availability and Partition Tolerance, e.g. that while changes may not appear immediately in AEM, it should always be available and should not fail due to networking issues.

The impact to integration tests is that unlike running a test against a local AEM instance, there's no guarantee that changes made in AEM as a Cloud Service are immediately reflected if your requests land on different servers running your instance.

Read Full Blog

5 Techniques to Modernize Your AEMCS Tests


Please use this thread to ask the related questions.