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?
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:
- 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.
- The second dependency is “junit-jupiter-api” which provides the JUnit 5 API for writing tests.
- 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:
-
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.
-
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.
-
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.
-
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.