May 3, 2020
Estimated Post Reading Time ~

AEM Mocks

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 a non-static field and use @BeforeEach and @AfterEach methods if you want to execute set up 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.

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
@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&param2=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 the 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 teardown code before or after the other tearDown action.

To define a 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.

To use a plugin in your unit test class, use the AemContextBuilder class instead of directly instantiating the AemContextclass. 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();

More examples:
wcm.io Sling Extensions Mock Helper
wcm.io Sling Extensions Mock Helper Test



By aem4beginner

No comments:

Post a Comment

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