AEM JUNIT5 with io.wcm.testing.mock.aem.junit5.AemContextExtension

io.wcm.testing.mock.aem.junit5.AemContextExtension is an extension for JUnit 5 that provides a way to run AEM (Adobe Experience Manager) unit tests with mocked AEM objects. This extension allows you to set up an AemContext object in your JUnit 5 test class, which provides access to mocked AEM objects such as the ResourceResolver, SlingSettings, and more. This makes it easier to test your code that interacts with AEM without actually requiring a running AEM instance. You can imagine, when using io.wcm.testing.mock.aem.junit5.AemContextExtension imported class, the JCR is virtually created in memory, you can think that this is a mock AEM JCR.


1. Exactly what is io.wcm.testing.mock.aem.junit5.AemContextExtension?

  • io.wcm.testing.mock.aem.junit5.AemContextExtension enables you to mock a in-memory AEM JCR, exactly like the AEM instance.
  • When mocking the JCR instance, you can imagine that this is an empty JCR, with no nodes inside of it, so you will need to use Java to add resources in the mock JCR repository.
  • Services like the pageManger, tagManger, resourceResolver, etc… will be available in the mock JCR instance.
  • io.wcm.testing.mock.aem.junit5.AemContextExtension is an extension for JUnit 5 that allows for running AEM (Adobe Experience Manager) unit tests with mocked AEM objects.
  • It provides a way to set up an AemContext object in a JUnit 5 test class, which gives access to mocked AEM objects such as the ResourceResolver, SlingSettings, and more.
  • This makes it easier to test code that interacts with AEM without requiring a running AEM instance.
  • It allows developers to write unit tests that can test their AEM-based code in isolation, without relying on a running AEM instance or a test environment set up with a specific version of AEM.
  • This extension can be used for testing AEM components, AEM service, AEM workflows and more.
  • It reduces the time and cost of testing by eliminating the need for a running AEM instance, and allows for faster test execution and more efficient development.

  • 2. What are the use cases for io.wcm.testing.mock.aem.junit5.AemContextExtension

    • Unit testing AEM-based projects without the need for a running AEM instance
    • Isolating your tests from the dependencies on a specific AEM version or environment
    • Mocking specific AEM objects, such as the ResourceResolver or SlingSettings, for more fine-grained control over the test environment
    • Simplifying the setup and tear-down of AEM-related test objects
    • Improving the reliability and repeatability of your tests by reducing external dependencies.

    Here’s an example of how to use AemContextExtension in a JUnit 5 test class:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    import static org.junit.jupiter.api.Assertions.assertEquals;
    import static org.junit.jupiter.api.Assertions.assertNotNull;

    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;

    import io.wcm.testing.mock.aem.junit5.AemContext;
    import io.wcm.testing.mock.aem.junit5.AemContextExtension;
    import org.apache.sling.api.resource.Resource;
    import org.apache.sling.api.resource.ResourceResolver;

    @ExtendWith(AemContextExtension.class)
    class MyTest {

        private final AemContext context = new AemContext();

        @Test
        void testMockResourceObjects() {
            // create mock resource objects
            context.create().resource("/content/object1", "prop1", "value1");
            context.create().resource("/content/object2", "prop1", "value2", "prop2", "value3");
            context.create().resource("/content/object3", "prop1", "value4", "prop2", "value5", "prop3", "value6");

            // get resource resolver
            ResourceResolver resourceResolver = context.resourceResolver();

            // test object 1
            Resource resource1 = resourceResolver.getResource("/content/object1");
            assertNotNull(resource1);
            assertEquals("value1", resource1.getValueMap().get("prop1", String.class));

            // test object 2
            Resource resource2 = resourceResolver.getResource("/content/object2");
            assertNotNull(resource2);
            assertEquals("value2", resource2.getValueMap().get("prop1", String.class));
            assertEquals("value3", resource2.getValueMap().get("prop2", String.class));

            // test object 3
            Resource resource3 = resourceResolver.getResource("/content/object3");
            assertNotNull(resource3);
            assertEquals("value4", resource3.getValueMap().get("prop1", String.class));
            assertEquals("value5", resource3.getValueMap().get("prop2", String.class));
            assertEquals("value6", resource3.getValueMap().get("prop3", String.class));
        }
    }

    2a. pom.xml

    These dependencies are what is being used in my pom.xml file for the example above.

    3 include dependencies:

    1. The first dependency is the “wcm-io-test-context-aem” library which provides the AemContext extension and utility classes for unit testing AEM-based Java code.
    2. The second dependency is “junit-jupiter-api” which provides the JUnit 5 API for writing tests.
    3. The third dependency is “junit-jupiter-engine” which provides the JUnit 5 TestEngine implementation for running tests.

    Keep in mind that these versions could be different based on the latest version of the dependencies, you should check the documentation for the latest versions before use… these are my settings.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <dependency>
      <groupId>io.wcm.testing</groupId>
      <artifactId>wcm-io-test-context-aem</artifactId>
      <version>3.3.0</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.7.0</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.7.0</version>
      <scope>test</scope>
    </dependency>

    3. Choosing the Right AemContext to Speed Up Tests

    The AemContextExtension allows for direct injection of the context into test methods, this can speed up test suite execution in cases where certain tests require the use of real JCR due to the use of indexes or other JCR features, while others do not. The more “mocked” the underlying resource resolver is, the faster the context will be. Therefore, it is recommended to use the following contexts in this order if possible:

    1. org.apache.sling.testing.mock.sling.junit5.NoResourceResolverTypeAemContext (fastest)

      • NoResourceResolverTypeAemContext is a type of AemContext that is used for tests that do not require a resource resolver.

      • When this type of AemContext is injected into a test method, it means that the test will not have access to any resource resolving functionality, such as resolving resources by path or querying the JCR.
      • This can be useful for tests that do not rely on any resources and only test pure Java logic.
      • This AemContext is the fastest of all the AemContexts because it does not have to instantiate a resource resolver and therefore it is lightweight, and can be useful when running test suites that requires a lot of test cases.
    2. org.apache.sling.testing.mock.sling.junit5.ResourceResolverMockAemContext (default when just AemContext is used)

      • ResourceResolverMockAemContext is an extension of AemContext which mocks the AEM resource resolver.
      • When you inject an AemContext of this type, it means that the resource resolver used within your test will be a mocked version, rather than the real resource resolver.
      • This can be useful for faster test execution and for isolating your tests from external dependencies.
      • It is the default context when using AemContext alone.
    3. org.apache.sling.testing.mock.sling.junit5.JcrMockAemContext (slower)

      • JcrMockAemContext is an extension of AemContext which mocks the AEM resource resolver.
      • Injecting AEMContext of JcrMockAemContext means that the test class is using a mocked version of the JCR for its AEM context; this means that the JCR functionality is being simulated in order to test the functionality of the code being tested, rather than utilizing a real JCR instance.
      • This can be useful for testing as it allows for faster execution and a more controlled environment.
      • The JcrMockAemContext also allows you to use JCR specific features, such as indexes, in your tests. This is useful when you need to test the functionality of your code that uses JCR indexes.
      • This is slower to compared to other AemContext because it simulates a full JCR repository, including the persistence layer. This means that it has to perform more operations and calculations in order to simulate the JCR repository, which can lead to slower test execution times. Additionally, if your tests are using features that require a real JCR repository, such as indexing or searching, this context may be slower as it is still a mocked version of the repository.
    4. org.apache.sling.testing.mock.sling.junit5.JcrOakAemContext (slowest)

      • Injecting AEMContext of JcrOakAemContext simulates a real JCR (Java Content Repository) with OakJCR.
      • This context is the slowest of all the AemContext options because it uses a real JCR and therefore takes more resources and time to run the test.
    1
    2
    3
    4
    private final AemContext noResourceResolverTypeAemContext = new AemContext(ResourceResolverType.NONE); // fastest
    private final AemContext resourceResolverMockAemContext = new AemContext(ResourceResolverType.RESOURCERESOLVER_MOCK); // default
    private final AemContext jcrMockAemContext = new AemContext(ResourceResolverType.JCR_MOCK); // slower
    private final AemContext jcrOakAemContext = new AemContext(ResourceResolverType.JCR_OAK); // slowest

    It is common to see tests where AemContext is stored in a field in a test class. However, if a single test requires JCR, this can slow down all other tests. With the ability to inject context as a test method parameter, previous tests will be as fast as before.


    Looking for Help? Or need a mentor?

    If you need help to super drive your AEM career road map, you can hire me for a one-hour session on codementor.io; We can work together on a price works just for you. https://www.codementor.io/@briankasingli. I'd be happy to help you along your way to becoming a high-quality AEM full-stack engineer.

    Was this post helpful?

    Hello, I am an enthusiastic Adobe Community Advisor and a seasoned Lead AEM Developer. I am currently serving as an AEM Technical Lead at MNPDigital.ca, bringing over a decade of extensive web engineering experience and more than eight years of practical AEM experience to the table. My goal is to give back to the AEM Full Stack Development community by sharing my wealth of knowledge with others. You can connect with me on LinkedIn.

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Back To Top