For testing (you do this, right?) your AEM project, most probably, you use AEM Mocks from wcm.io. This is a powerful library, which makes your life easier when it comes to testing Sling Models, Servlets, other classes which work with Page and other entities from AEM.
How to use AemContextCallback
Interface AemContextCallback allows us to define some default operations which are common for all our test classes, e.g. to register our models.
import io.wcm.testing.mock.aem.junit.AemContext; import io.wcm.testing.mock.aem.junit.AemContextCallback; public class AppContextSetup implements AemContextCallback { @Override public void execute(final AemContext aemContext) throws Exception { aemContext.addModelsForPackage("com.taradevko.aem"); } }
Now, when we define JUnit rule for AemContext, we should create and pass our callback into AemContext‘s constructor.
public final AppContextSetup appContextSetup = new AppContextSetup(); @Rule public AemContext aemContext = new AemContext(appContextSetup, ResourceResolverType.RESOURCERESOLVER_MOCK);
After these changes, all our models in the defined package will be initialized. There is no need to do this explicitly anymore.
Mixing AemContextCallback and TestRule
So far, our callback does not make our life much easier, but we definitely can take more out of AemContextCallback! When you need to create some resource for a class under the test, you either build it directly in your test class (or some helper) or you load it from the file (e.g. json). We can easily move this logic into or callback implementing additionally JUnit’s TestRule interface. Let’s take a look at this:
import io.wcm.testing.mock.aem.junit.AemContext; import io.wcm.testing.mock.aem.junit.AemContextCallback; import org.apache.sling.api.resource.Resource; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; public class AppContextSetup implements AemContextCallback, TestRule { private Class testClass; @Override public void execute(final AemContext aemContext) throws Exception { aemContext.addModelsForPackage("com.taradevko.aem"); final Resource content = aemContext.load().json( String.format("/%s/%s.json", testClass.getPackage().getName(), testClass.getSimpleName()), "/content"); aemContext.currentResource(content); } @Override public Statement apply(final Statement base, final Description description) { testClass = description.getTestClass(); return base; } }
So what is going on here? Method apply is called for each test in our test class. Here we just store this class to reuse later.
Then, when AemContext executes our callback, we build a path “/class_package/class_name.json”, load it with ContentLoader and set as a current resource.
Time for testing
Now it’s time to use it. Imagine, we have a simple model we want to test:
import org.apache.sling.api.resource.Resource; import org.apache.sling.models.annotations.Model; import javax.inject.Inject; @Model(adaptables = Resource.class) public class SomeModel { @Inject private String property1; public String doSmthWithProperty() { return property1.toUpperCase(); } }
And also we prepared a json file with a resource to adapt to this model for test purposes. It’s located under “com.taradevko.aem.contcallback/SomeModelTest.json” in test resources section.
{ "jcr:primaryType": "nt:unstructured", "property1": "jsonProp1Value" }
And now, finally – testing:
package com.taradevko.aem.contcallback; import com.taradevko.aem.AppContextSetup; import io.wcm.testing.mock.aem.junit.AemContext; import org.apache.sling.testing.mock.sling.ResourceResolverType; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; public class SomeModelTest { @Rule public final AppContextSetup appContextSetup = new AppContextSetup(); @Rule public AemContext aemContext = new AemContext(appContextSetup, ResourceResolverType.RESOURCERESOLVER_MOCK); @Test public void demoTest() { final SomeModel someModel = aemContext.currentResource().adaptTo(SomeModel.class); assertEquals("Should contain value from json", someModel.doSmthWithProperty(), "JSONPROP1VALUE"); } }
As before, we create AppContextSetup and pass it to the constructor of AemContext. But now it also got annotation @Rule.
Now, if we run the example, we can see that test passed successfully!
Now, whenever we need to load resources from json, we just should put it on the correct path, and use our AppContextSetup. The rest will be done automatically!
What is next
You can do much more with AemContextCallback, like registering you stub implementations of OSGi services or executing additional callbacks on AemContext.
You can find the complete example in this repository.