Usage
The AemContext
object provides access to mock implementations of:
- OSGi Component Context
- OSGi Bundle Context
- Sling Resource Resolver
- Sling Request
- Sling Response
- Sling Script Helper
Additionally it supports:
- Registering OSGi services
- Registering adapter factories
- Accessing JSON Importer
JUnit 5: AEM Context JUnit Extension
The AEM mock context can be injected into a JUnit test using a custom JUnit extension named AemContextExtension
. This extension takes care of all initialization and cleanup tasks required to make sure all unit tests can run independently (and in parallel, if required).
Example:
@ExtendWith(AemContextExtension.class)
public class ExampleTest {
private final AemContext context = new AemContext();
@Test
public void testSomething() {
Resource resource = context.resourceResolver().getResource("/content/sample/en");
Page page = resource.adaptTo(Page.class);
// further testing
}
}
It is possible to combine such a unit test with a @ExtendWith
annotation e.g. for Mockito JUnit Jupiter Extension.
It is recommended to define the AemContext field as non-static field and use @BeforeEach
and @AfterEach
methods if you want to execute setup or tear down code for each test run. Since version 3.0.0 AEM Mocks also supports static AemContext fields and @BeforeAll
and @AfterAll
methods. However, you have to make sure you have no side-effects between the tests, as all changes in the AemContext object (e.g. content written to repository or OSGi services registered) are visible to all tests in the class. You should never try to instantiate an AemContext object within a @BeforeEach
or @BeforeAll
method, this may lead to duplicate context instances.
JUnit 4: AEM Context JUnit Rule
The AEM mock context can be injected into a JUnit test using a custom JUnit rule named AemContext
. This rule takes care of all initialization and cleanup tasks required to make sure all unit tests can run independently (and in parallel, if required).
Example:
public class ExampleTest {
@Rule
public final AemContext context = new AemContext();
@Test
public void testSomething() {
Resource resource = context.resourceResolver().getResource("/content/sample/en");
Page page = resource.adaptTo(Page.class);
// further testing
}
}
It is possible to combine such a unit test with a @RunWith
annotation e.g. for Mockito JUnit Runner.
Choosing Resource Resolver Mock Type
The AEM mock context supports different resource resolver types (provided by the Sling Mocks implementation). Example:
private final AemContext context = new AemContext(ResourceResolverType.RESOURCERESOLVER_MOCK);
Different resource resolver mock types are supported with pros and cons, see Resource Resolver Types for details.
It is even possible to supply multiple resource resolver types in the constructor argument - in this case the unit test is run multiple times, once for each type. But this is only relevant if you want to develop your own unit test support components that should be compatible with all resource resolver types. Normally you pick just one type which fits best for your testing needs.
Getting and Manipulating Pages
Example for accessing AEM API for reading and writing data:
@ExtendWith(AemContextExtension.class)
public class ExampleTest {
private final AemContext context = new AemContext();
@Test
public void testSomething() {
Page page = context.pageManager().getPage("/content/sample/en");
Template template = page.getTemplate();
Iterator<Page> childPages = page.listChildren();
// further testing
}
@Test
public void testPageManagerOperations() throws WCMException {
Page page = context.pageManager().create("/content/sample/en", "test1",
"/apps/sample/templates/homepage", "title1");
// further testing
context.pageManager().delete(page, false);
}
}
Simulating Sling Request
Example for preparing a sling request with custom request data:
// prepare sling request
context.request().setQueryString("param1=aaa¶m2=bbb");
context.requestPathInfo().setSelectorString("selector1.selector2");
context.requestPathInfo().setExtension("html");
// set current page
context.currentPage("/content/sample/en");
// set WCM Mode
WCMMode.EDIT.toRequest(context.request());
Registering OSGi service
Example for registering and getting an OSGi service for a unit test:
// register OSGi service
context.registerService(MyClass.class, myService);
// or alternatively: inject dependencies, activate and register OSGi service
context.registerInjectActivateService(myService);
// get OSGi service
MyClass service = context.getService(MyClass.class);
// or alternatively: get OSGi service via bundle context
ServiceReference ref = context.bundleContext().getServiceReference(MyClass.class.getName());
MyClass service2 = context.bundleContext().getService(ref);
Adapter Factories
You can register your own or existing adapter factories to support adaptions e.g. for classes extending SlingAdaptable
.
Example:
// register adapter factory
context.registerService(myAdapterFactory);
// test adaption
MyClass object = resource.adaptTo(MyClass.class);
You do not have to care about cleaning up the registrations - this is done automatically by the AemContext
rule.
Sling Models
Example:
@Before
public void setUp() {
// register models from package
context.addModelsForPackage("com.app1.models");
}
@Test
public void testSomething() {
RequestAttributeModel model = context.request().adaptTo(RequestAttributeModel.class);
// further testing
}
@Model(adaptables = SlingHttpServletRequest.class)
interface RequestAttributeModel {
@Inject
String getProp1();
}
Note: If your model is not adaptable from SlingHttpServletRequest.class (e.g. only from Resource.class) and you rely on the extra features provided by wcm.io Sling Commons and wcm.io Sling Models which can inject request-derived objects via a ThreadLocal it might be necessary to set the request context manually to ensure injection of request-derived objects works in your unit tests. Example:
MockSlingExtensions.setRequestContext(context, context.request());
Content Policies
AEM Mock does not implement the full stack with editable templates, policy mappings and policies stored in the repository. But it provides a shortcut way to quickly provide a content policy with some properties for any resource type, and ensures these properties can be read either via Style objects or via the Content Policy API.
Example for setting a content policy for a resource type:
// create a content policy with mapping for resource type
context.contentPolicyMapping("app1/componenty/component1",
"prop1", "value1",
"prop2", 123);
Setting run modes
Example:
// set runmode for unit test
context.runMode("author");
This sets the current run mode(s) in a mock version of SlingSettingsService
.
Application-specific AEM context
When building unit test suites for your AEM application you have usually to execute always some application-specific preparation tasks, e.g. register custom services, adapter factories or import sample content. This can be done in a convenience class using a SetupCallback
. Example:
public final class AppAemContext {
public static AemContext newAemContext() {
return new AemContext(new SetUpCallback());
}
private static final class SetUpCallback implements AemContextCallback {
@Override
public void execute(AemContext context) throws PersistenceException, IOException {
// application-specific services for unit tests
context.registerService(AdapterFactory.class, new AppAdapterFactory());
context.registerService(new AemObjectInjector());
// import sample content
context.contentLoader().json("/sample-content.json", "/content/sample/en");
// set default current page
context.currentPage("/content/sample/en");
}
}
}
In the unit test you can use this customized AEM context:
@ExtendWith(AemContextExtension.class)
public class MyTest {
private final AemContext context = AppAemContext.newAemContext();
@Test
public void testSomething() {
// do test
}
}
Context Plugins
AEM Mocks supports “Context Plugins” that hook into the lifecycle of each test run and can prepare test setup before or after the other setUp actions, and execute test tear down code before or after the other tearDown action.
To use a plugin in your unit test class, use the AemContextBuilder
class instead of directly instantiating the AemContext
class. This allows you in a fluent style to configure more options, with the plugin(...)
method you can add one or more plugins.
Example:
AemContext context = new AemContextBuilder().plugin(MY_PLUGIN).build();
There are a couple of predefined plugins, that automatically register all required OSGi services required to run a certain library features in unit tests:
To define your own plugin implement the org.apache.sling.testing.mock.osgi.context.ContextPlugin<AemContextImpl>
interface. For convenience it is recommended to extend the abstract class org.apache.sling.testing.mock.osgi.context.AbstractContextPlugin<AemContextImpl>
. In most cases you would just override the afterSetUp
method. In this method you can register additional OSGi services or do other preparation work. It is recommended to define a constant pointing to a singleton of a plugin instance for using it.
More examples: