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.
Solved! Go to Solution.
Views
Replies
Total Likes
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)
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);
https://experienceleaguecommunities.adobe.com/t5/adobe-experience-manager/runtime-error-while-runnin...
Hope this may helps you!
Regards,
Santosh
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.
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.
Hi @RobertHarper,
Can you try the below.
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()); } }
Not only does that not work. It will not compile. The Reference annotation may not be applied to a method.
Hi @RobertHarper,
If you are using org.osgi.service.component.annotations.Reference, you can annotate it to a method.
Check this - http://docs.osgi.org/javadoc/r6/residential/org/osgi/service/component/annotations/Reference.html
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.
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.
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.
@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.
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 ✌
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.
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!
Actually, this didn't work. I forgot to comment out where I am manually adding the ResourceResolverFactory.
Hi @RobertHarper , If you can send me sample test class how you are trying to do will help.
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.
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.
Can you share the production code (the class only) and the test class? It's probably easier to help then.
I don't think I would be allowed to do that. I'll try it with the sample servlet.
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()); } }
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)
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);