1
votes

I'm trying to write an integration test using wcm.io Testing AEM Mocks

The code I want to test saves some data in the repository so I would like to verify if the right content is actually stored after I run a tested method.

In my previous tests, I used the JCR_MOCK Resource Resolver Type, like this:

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {

    @Rule
    public final AemContext aemContext = new AemContext(ResourceResolverType.JCR_MOCK);

    private MyClass tested;

    @Before
    public void setUp() throws Exception {

        //here, I load an entire page,
        //from which my tested class reads some data
        aemContext.load().json("/jcrdata/myPage.json", "/etc/mystuff/myTool");

        tested = new MyClass(aemContext.resourceResolver());
    }

    @Test
    public void interactWithTheRepository() {

        SomeResult result = tested.interactWithTheRepository();

        //assertions here
    }
}

This works just fine as long as I just want to read data from the mock repository or pretend I save something. The problem starts when I actually want to verify that some data has been persisted in the repository by the tested class.

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {

    @Rule
    public final AemContext aemContext = new AemContext(ResourceResolverType.JCR_MOCK);

    private MyClass tested;

    @Before
    public void setUp() throws Exception {

        //here, I load an entire page,
        //on which the instance of my tested class later saves some data
        aemContext.load().json("/jcrdata/myPage.json", "/etc/mystuff/myTool");

        tested = new MyClass(aemContext.resourceResolver());
    }

    @Test
    public void storeDataInTheRepository() {

        tested.storeDataInTheRepository();

        //this comes up as null because the JCR_Mock resolver
        //does not use an actual repository and nothing is stored
        Resource result = aemContext.resourceResolver().getResource("/etc/mystuff/myTool/somethingSavedByMyClassInstance");

        //assertions here
    }
}

I believe this can achieved with the JCR_JACKRABBIT Resource Resolver Type, which uses an actual repository. However, I'm having a hard time loading the data into the mock repository. As soon as I use JCR_JACKRABBIT instaed of JCR_MOCK, my setUp method fails with a NullPointerException (other parts of the class omitted for clarity)

@Rule
public final AemContext aemContext = new AemContext(ResourceResolverType.JCR_JACKRABBIT);

@Before
public void setUp() throws Exception {

     //This line fails with an NPE
    aemContext.load().json("/jcrdata/myPage.json", "/etc/mystuff/myTool");

    tested = new MyClass(aemContext.resourceResolver());
}

I debugged the code and noticed that it fails while trying to create the resource hierarchy.

java.lang.NullPointerException
    at org.apache.sling.resourceresolver.impl.ResourceResolverImpl.create(ResourceResolverImpl.java:1044)
    at org.apache.sling.testing.mock.sling.loader.ContentLoader.createResourceHierarchy(ContentLoader.java:183)
    at org.apache.sling.testing.mock.sling.loader.ContentLoader.createResourceHierarchy(ContentLoader.java:178)
    at org.apache.sling.testing.mock.sling.loader.ContentLoader.json(ContentLoader.java:155)
    at org.apache.sling.testing.mock.sling.loader.ContentLoader.json(ContentLoader.java:120)
    at com.foo.bar.baz.MyClassTest.setUp(MyClassTest.java:35)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
    at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

According to the documentation, Sling Testing should handle the creation of all parent resources while trying to load a page from a JSON document:

Resource org.apache.sling.testing.mock.sling.loader.ContentLoader.json(String classpathResource, String destPath)

Import content of JSON file into repository. Auto-creates parent hierarchies as nt:unstrucured nodes if missing.

However, when the root of the repository is reached and an etc node is supposed to be created under /, the ContentLoader's method createResourceHierarchy calls the ResourceResolver with the following parameters:

resourceResolver.create(null, ResourceUtil.getName("/etc"), props);

where props is a simple HashMap of the following structure: {jcr:primaryType=nt:unstructured}, causing a NullPointerException (due to a null parent resource being passed)

I'm not sure if this is a bug or something I'm doing wrong, the Sling Testing documentation mentions this about the JCR_JACKRABBIT Resource Resolver type:

To import Sling content you have to fully register all node types required for the data

but I'm not sure how to interpret this.

I'm using:

  • io.wcm.testing.aem-mock 1.2.2
  • org.apache.sling.testing.sling-mock-jackrabbit 0.1.0

How can I load content from a JSON file when using the JCR_JACKRABBIT Resource Resolver implementation?

2

2 Answers

2
votes

Thanks to Thomas' answer I was able to take a couple steps forward but kept facing different errors. In the meantime, I opened a topic on the wcm.io Developers mailing group to learn if a more approachable way of achieving my goals was possible. The answer is divided into two parts. The first one describes what can be done to write tests for the specific use case I was trying to cover. The latter describes my findings on actually using the JCR_JACKRABBIT resource resolver type.

Testing Classes That Write Into the Repository

It turns out I was too fast in switching from JCR_MOCK to JCR_JACKRABBIT resource resolver type. JCR_MOCK is sufficient for my case, which is the following:

  1. Load content from a JSON file on the classpath
  2. Execute a method of the tested class against the mock repository (the class is supposed to read some content from the repo and store some more into it)
  3. Read the content written by the tested class and assert if it matches the expected values

The data IS actually written into the in-memory repository. The fact I couldn't read it was a result of my own error rather than the implementation used.

Loading JSON Into a JCR_JACKRABBIT-backed AemContext

I managed to get quite far following the advice from Thomas' answer but I haven't succeeded in having this implementation work completely smoothly. Here's what I did:

I added the following lines to my setUp method:

Session session = aemContext.resourceResolver().adaptTo(Session.class); 
RepositoryUtil.registerNodeType(session, getClass().getResourceAsStream("/nodetypes/types.cnd")); 

just before aemContext.load().json("/jcrdata/myPage.json", "/etc/mystuff/myTool");

types.cnd is a file on my classpath that contains the node type definitions as found in the CRX repository on my AEM instance.

This allowed me to get rid of the NPE but for some reason the cq:Page and cq:PageContent node types do not get recognized. This results in a PersistenceException being thrown upon calling aemContext.load().json("/jcrdata/myPage.json", "/etc/mystuff/myTool"); if a node of either of those types is present in the JSON loaded.

The problem seems to be that the cq:Page and cq:PageContent node types still cannot be found despite the fact that I explicitly add them in my types.cnd file. I took the definitions of these two types from docs.adobe.com and I made sure to include the namespace prefixes used in those definitions.

Here's an example stack trace that appears when I havea cq:Page in my content JSON:

java.lang.RuntimeException: org.apache.sling.api.resource.PersistenceException: Unable to create node at /etc/foo/bar/baz
    at org.apache.sling.testing.mock.sling.loader.ContentLoader.json(ContentLoader.java:167)
    at org.apache.sling.testing.mock.sling.loader.ContentLoader.json(ContentLoader.java:120)
    at com.foo.bar.baz.MyClassTest.setUp(MyClassTest.java:42)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
    at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.apache.sling.api.resource.PersistenceException: Unable to create node at /etc/foo/bar/baz
    at org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProvider.create(JcrResourceProvider.java:473)
    at org.apache.sling.resourceresolver.impl.tree.ResourceProviderEntry.create(ResourceProviderEntry.java:479)
    at org.apache.sling.resourceresolver.impl.ResourceResolverImpl.create(ResourceResolverImpl.java:1053)
    at org.apache.sling.testing.mock.sling.loader.ContentLoader.createResource(ContentLoader.java:217)
    at org.apache.sling.testing.mock.sling.loader.ContentLoader.json(ContentLoader.java:163)
    ... 30 more
Caused by: javax.jcr.nodetype.NoSuchNodeTypeException: {http://www.day.com/jcr/cq/1.0}Page
    at org.apache.jackrabbit.core.nodetype.NodeTypeRegistry.getEffectiveNodeType(NodeTypeRegistry.java:1024)
    at org.apache.jackrabbit.core.nodetype.NodeTypeRegistry.getEffectiveNodeType(NodeTypeRegistry.java:487)
    at org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl.getNodeType(NodeTypeManagerImpl.java:177)
    at org.apache.jackrabbit.core.NodeImpl.addNode(NodeImpl.java:1240)
    at org.apache.jackrabbit.core.session.AddNodeOperation.perform(AddNodeOperation.java:111)
    at org.apache.jackrabbit.core.session.AddNodeOperation.perform(AddNodeOperation.java:37)
    at org.apache.jackrabbit.core.session.SessionState.perform(SessionState.java:200)
    at org.apache.jackrabbit.core.ItemImpl.perform(ItemImpl.java:91)
    at org.apache.jackrabbit.core.NodeImpl.addNodeWithUuid(NodeImpl.java:1769)
    at org.apache.jackrabbit.core.NodeImpl.addNode(NodeImpl.java:1729)
    at org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProvider.create(JcrResourceProvider.java:435)
    ... 34 more

I can, however, successfully import the content as soon as I replace all cq:Page and cq:PageContent types with nt:unstructured This allows me to execute my code against the repository and read the content that my tested class writes there but I'm a bit concerned that the fact my test content has different node types than its real counterpart might affect the test results in some cases.

JCR_JACKRABBIT also requires a Derby database to be created, which does not get cleared between test executions.

All in all, JCR_JACKRABBIT introduces some problems and does not have any advantages over JCR_MOCK in my specific case. I was recommended against using it unless I really need to mock advanced functionalities not supported by JCR_MOCK, such as events or versioning.

To quote Stefan Seifert's comment on the wcm.io Developers mailing group

use JCR_JACKRABBIT only if you need special features not supported by JCR_MOCK, e.g. eventing, versioning etc. but only then, it’s the most heavyweight and most inperformant choice and not used much currently. we did some small improvements on this integration in the current trunk of sling-mock-jackrabbit in sling, esp. concering the content loader and special properties that cannot be imported, but this is not released yet.

1
votes

It tells you that your implementation is unware of a distinct nodetype in your structure and it's constraints and is unable to create such a node.

Have you tried to place a nodetype definition (nodetype.cnd) in your classpath (just put it into the src/test/resources directory) and to load it using the RepositoryUtil?

RepositoryUtil.registerNodeType(session, getClass().getResourceAsStream("nodetypes.cnd"));