Expand my Community achievements bar.

Guidelines for the Responsible Use of Generative AI in the Experience Cloud Community.
SOLVED

junit5 and Mockito tests not injecting the ResourceResolverFactory

Avatar

Level 3

I have been updating out code base with unit tests and they have some Sling servlets that are obtaining the service user based ResourceResolver for extra work and making calls to other services. The issue I'm having is that the servlet uses @reference ResourceResolverFactory resourceResolverFactory to obtain an instance of the factory. In my tests, I have tried AemContext.registerService(ResourceResolverFactory.class, resourceResolverInstance) to attempt to set the mocked factory. The servlet is not resolving the service so I am unable to perform a proper test.

I am using the mockito-core 3.3.3 , org.apache.sling.testing.osgi-mock.junit5 3.1.2, io.wcm.testing.aem-mock.junit5 3.0.2, mockito-junit-jupiter 3.3.3 dependencies. Many of the on line samples don't work with these versions because packaging and classes have changed.

 

Help with resources and what I need to do in order to get the @reference annotation to include the testing instance of the ResourceResolverFactory would be fantastic.

1 Accepted Solution

Avatar

Correct answer by
Community Advisor

Hi @RobertHarper,

For the snippet that you have shared now, here is the Test class, you can use this as is and check if it works now. 

Changes done (on top of what you shared)

  • Mocked ResourceResolverFactory (via @Mock)
    • @Mock
      private ResourceResolverFactory rescResolverFactory;
  • Added MockitoExtension.class to @ExtendWith (for using @Mock)
    • @ExtendWith({AemContextExtension.class, MockitoExtension.class})
  • Removed the AemContext from doGet argument and instantiated explicitly with ResourceResolverType as RESOURCERESOLVER_MOCK
    • private AemContext context = new AemContext(ResourceResolverType.RESOURCERESOLVER_MOCK);
package com.mysite.core.servlets;

import java.io.IOException;

import javax.servlet.ServletException;

import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.testing.mock.sling.ResourceResolverType;
import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse;
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.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith({AemContextExtension.class, MockitoExtension.class})
class SimpleServletTest {

    @Mock
    private ResourceResolverFactory resourceResolverFactory;

    private AemContext context = new AemContext(ResourceResolverType.RESOURCERESOLVER_MOCK);

    @InjectMocks
    private SimpleServlet fixture = new SimpleServlet();

    @Test
    void doGet() throws ServletException, IOException {
        context.build().resource("/content/test", "jcr:title", "resource title").commit();
        context.currentResource("/content/test");

        MockSlingHttpServletRequest request = context.request();
        MockSlingHttpServletResponse response = context.response();

        fixture.doGet(request, response);

        assertEquals("Title = resource title", response.getOutputAsString());
    }
}

Once when you see the above working, you can try the below. 

 

For creating ResourceResolver out of this mocked ResourceResolverFactory, we need to introduce when and thenReturn  

Map<String, Object> map = new HashMap<>();
map.put(ResourceResolverFactory.SUBSERVICE, "demoproject-service");
/* Getting Resolver from current AemContext */
ResourceResolver resourceResolver = context.request().getResourceResolver();
/*Using the resolver from previous step for thenReturn */
when(rescResolverFactory.getServiceResourceResolver(map)).thenReturn(resourceResolver);

Update : (Corrected the typo on variable name of mocked ResourceResolverFactory)

when(resourceResolverFactory.getServiceResourceResolver(map)).thenReturn(resourceResolver);

 

View solution in original post

24 Replies

Avatar

Level 3

I am not encountering a runtime error so that isn't any help. The problem is that the mocked ResourceResolverFactor instance is not being supplied when the servlet's doGet() method is called from the test case.

Avatar

Level 3

FWIW, I can replicate the problem after creating an archetype 27 project, adding a line

@Reference
ResourceResolverFactory resourceResolverFactory;

line in the sample serlvet. Then in the sample test code, mock an instance of the ResourceResolverFactory register the service with the AemContext then run the test code.

Avatar

Community Advisor

Hi @RobertHarper,

Can you try the below. 

  • In your actual Servlet class, instead of using @Reference to an instance variable, annotate it to a method (method with name - set/bind)
  • With above in place, you can invoke this method in Test class and set the mocked ResourceResolverFactory
  • Cross check if your AemContext is created with ResourceResolverType as RESOURCERESOLVER_MOCK
  • Test class is annotated with @ExtendWith (both AemContextExtension and MockitoExtension)

Example snippet (amended the SimpleServlet class that comes with archetype project - mine is older, should be archetype 22 or 23)

SimpleServlet.java

@Component(service=Servlet.class,
           property={
                   "sling.servlet.methods=" + HttpConstants.METHOD_GET,
                   "sling.servlet.resourceTypes="+ "demoproject/components/structure/page",
                   "sling.servlet.extensions=" + "txt"
           })
@ServiceDescription("Simple Demo Servlet")
public class SimpleServlet extends SlingSafeMethodsServlet {

    private static final long serialVersionUID = 1L;
    private final Logger LOG = LoggerFactory.getLogger(this.getClass());

    private ResourceResolverFactory rescResolverFactory;

    @Reference
    public void setResourceResolverFactory(ResourceResolverFactory rescResolverFactory){
        this.rescResolverFactory = rescResolverFactory;
    }

    @Override
    protected void doGet(final SlingHttpServletRequest req,
            final SlingHttpServletResponse resp) throws ServletException, IOException {

        Map<String, Object> map = new HashMap<>();
        map.put(ResourceResolverFactory.SUBSERVICE, "demoproject-service");
        String rescPath = null;

        try(ResourceResolver resourceResolver = rescResolverFactory.getServiceResourceResolver(map)){
            Resource demoResc = resourceResolver.getResource("/content/demo");
            if (demoResc != null) {
                rescPath = demoResc.getPath();
                LOG.info("demo Resource path={}", demoResc.getPath());
            }
        } catch (LoginException e) {
            LOG.error("LoginException={}", e.getMessage());
        }
        final Resource resource = req.getResource();
        resp.setContentType("text/plain");
        resp.getWriter().write("Title = " + resource.getValueMap().get(JcrConstants.JCR_TITLE) +  " and path=" + rescPath);
    }
}

SimpleServletTest.java

@ExtendWith({AemContextExtension.class, MockitoExtension.class})
class SimpleServletTest {

    private SimpleServlet fixture = new SimpleServlet();

    private AemContext context = new AemContext(ResourceResolverType.RESOURCERESOLVER_MOCK);

    @Mock
    private ResourceResolverFactory rescResolverFactory;

    @Test
    void doGet() throws ServletException, IOException, LoginException {

        Map<String, Object> map = new HashMap<>();
        map.put(ResourceResolverFactory.SUBSERVICE, "demoproject-service");

        context.build().resource("/content/demo", "jcr:title", "resource title").commit();
        context.currentResource("/content/demo");

        MockSlingHttpServletRequest request = context.request();
        MockSlingHttpServletResponse response = context.response();
        ResourceResolver resourceResolver = context.request().getResourceResolver();
        fixture.setResourceResolverFactory(rescResolverFactory);
        when(rescResolverFactory.getServiceResourceResolver(map)).thenReturn(resourceResolver);
        fixture.doGet(request, response);

        assertEquals("Title = resource title and path=/content/demo", response.getOutputAsString());
    }
}

 

Avatar

Level 3

Not only does that not work. It will not compile. The Reference annotation may not be applied to a method.

Avatar

Level 3

If I use that import, it completely breaks the build for some reason. Other classes will not compile. I'm not sure why. I think it might interfere with the lombok annotations for the gettter and setter annotations. I don't quite understand why it would effect other classes but for some reason, the compiler is breaking.

Avatar

Level 3

I really hate doing it but I'm holding my nose and adding a method to directly set the ResourceResolverFactory and I'll go on from there. This feels so wrong on so many levels.

Avatar

Community Advisor

Those are OSGI R6 annotations, it comes OOTB (respective maven dependency entry) with archetype projects.

Also, I use lombok as well in my project code base.

Is it possible for you to share the build error. 

 

Outside this, @InjectMocks on Servlet instantiation suggested by @MohitKumarK will work as well. 

If you still face issues, share the servlet and test snippet if possible.  

Avatar

Community Advisor

@RobertHarper Normally to mock any service @Mock annotation should suffice. I am not sure why it is not resolving for you. But incase it helps , pasting below some links I found in WWW.

 

https://experienceleaguecommunities.adobe.com/t5/adobe-experience-manager/mock-private-resourceresol...

 

https://experienceleaguecommunities.adobe.com/t5/adobe-experience-manager/creating-unit-test-cases-f...

 

Try this if @Mock is not working 

https://stackoverflow.com/questions/36087728/get-reference-to-mockresourceresolverfactory

 

A sample JUnit in which Resolver Factory is used https://aemhints.com/2020/12/19/junit-tests-in-aem-6-5-with-aem-context/

 

Thanks

Veena ✌

 

 

Avatar

Level 3

I have seen all of those and none of them have worked. No matter what I do, when the servlet is created, the ResourceResolverFactory is not resolved. The test context includes an instance of a mocked ResourceResolverFactory by default but it is not being injected with the reference annotation. If it would, I could proceed with the when().thenReturn() stuff.

Avatar

Level 4

Hi @RobertHarper ,

while including references as mock objects you need to use injectMocks for your actual servlet class. Take an example myServlet is using ResourceResolverFactory your code would look like below.

 

@Mock

ResourceResolver resolver;

 

@Mock

ResourceResolverFactory factory;

 

@InjectMocks

MyServlet myServlet = new MyServlet ();

 

and you can use lenient when method to simulate mock on factory to give resource resolver

 

lenient().when(factor.getServiceResourceResolver(anyMap())).thenReturn(resolver);

 

note that this is not exact syntax.

 

thanks!

Avatar

Level 3

Actually, this didn't work. I forgot to comment out where I am manually adding the ResourceResolverFactory.

Avatar

Level 4

Hi @RobertHarper , If you can send me sample test class how you are trying to do will help.

Avatar

Employee Advisor

If you use AemContext, you don't need to register your own mocked version of the ResourceResolver, because there is already one pre-registered in the context.

Avatar

Level 3

I know. I'm just trying everything I see to get it to work. So far, nothing resolves the issue unless I add a method to set it up myself.

Avatar

Employee Advisor

Can you share the production code (the class only) and the test class? It's probably easier to help then.

Avatar

Level 3

I don't think I would be allowed to do that. I'll try it with the sample servlet.

Avatar

Level 3

Here is the code from a sample project that also doesn't work. I just made a few slight modifications of the sample provided in the archetype. Again, the ResourceResolverFactory is not resolved during the test run.

/*
 *  Copyright 2015 Adobe Systems Incorporated
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.mysite.core.servlets;

import com.day.cq.commons.jcr.JcrConstants;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.propertytypes.ServiceDescription;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;

/**
 * Servlet that writes some sample content into the response. It is mounted for
 * all resources of a specific Sling resource type. The
 * {@link SlingSafeMethodsServlet} shall be used for HTTP methods that are
 * idempotent. For write operations use the {@link SlingAllMethodsServlet}.
 */
@Component(service = { Servlet.class })
@SlingServletResourceTypes(
        resourceTypes="mysite/components/page",
        methods=HttpConstants.METHOD_GET,
        extensions="txt")
@ServiceDescription("Simple Demo Servlet")
public class SimpleServlet extends SlingSafeMethodsServlet {

    @Reference
    ResourceResolverFactory resourceResolverFactory;

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(final SlingHttpServletRequest req,
            final SlingHttpServletResponse resp) throws ServletException, IOException {
        System.out.println(this.getClass().getSimpleName() + ".resourceResolverFactory = " + resourceResolverFactory);
        final Resource resource = req.getResource();
        resp.setContentType("text/plain");
        resp.getWriter().write("Title = " + resource.getValueMap().get(JcrConstants.JCR_TITLE));
    }
}


/*
 *  Copyright 2018 Adobe Systems Incorporated
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.mysite.core.servlets;

import java.io.IOException;

import javax.servlet.ServletException;

import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse;
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.mockito.InjectMocks;

import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith(AemContextExtension.class)
class SimpleServletTest {

    @InjectMocks
    private SimpleServlet fixture = new SimpleServlet();

    @Test
    void doGet(AemContext context) throws ServletException, IOException {
        context.build().resource("/content/test", "jcr:title", "resource title").commit();
        context.currentResource("/content/test");

        MockSlingHttpServletRequest request = context.request();
        MockSlingHttpServletResponse response = context.response();

        fixture.doGet(request, response);

        assertEquals("Title = resource title", response.getOutputAsString());
    }
}

Avatar

Correct answer by
Community Advisor

Hi @RobertHarper,

For the snippet that you have shared now, here is the Test class, you can use this as is and check if it works now. 

Changes done (on top of what you shared)

  • Mocked ResourceResolverFactory (via @Mock)
    • @Mock
      private ResourceResolverFactory rescResolverFactory;
  • Added MockitoExtension.class to @ExtendWith (for using @Mock)
    • @ExtendWith({AemContextExtension.class, MockitoExtension.class})
  • Removed the AemContext from doGet argument and instantiated explicitly with ResourceResolverType as RESOURCERESOLVER_MOCK
    • private AemContext context = new AemContext(ResourceResolverType.RESOURCERESOLVER_MOCK);
package com.mysite.core.servlets;

import java.io.IOException;

import javax.servlet.ServletException;

import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.testing.mock.sling.ResourceResolverType;
import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse;
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.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith({AemContextExtension.class, MockitoExtension.class})
class SimpleServletTest {

    @Mock
    private ResourceResolverFactory resourceResolverFactory;

    private AemContext context = new AemContext(ResourceResolverType.RESOURCERESOLVER_MOCK);

    @InjectMocks
    private SimpleServlet fixture = new SimpleServlet();

    @Test
    void doGet() throws ServletException, IOException {
        context.build().resource("/content/test", "jcr:title", "resource title").commit();
        context.currentResource("/content/test");

        MockSlingHttpServletRequest request = context.request();
        MockSlingHttpServletResponse response = context.response();

        fixture.doGet(request, response);

        assertEquals("Title = resource title", response.getOutputAsString());
    }
}

Once when you see the above working, you can try the below. 

 

For creating ResourceResolver out of this mocked ResourceResolverFactory, we need to introduce when and thenReturn  

Map<String, Object> map = new HashMap<>();
map.put(ResourceResolverFactory.SUBSERVICE, "demoproject-service");
/* Getting Resolver from current AemContext */
ResourceResolver resourceResolver = context.request().getResourceResolver();
/*Using the resolver from previous step for thenReturn */
when(rescResolverFactory.getServiceResourceResolver(map)).thenReturn(resourceResolver);

Update : (Corrected the typo on variable name of mocked ResourceResolverFactory)

when(resourceResolverFactory.getServiceResourceResolver(map)).thenReturn(resourceResolver);