Sling model unit test with constructor | Community
Skip to main content
October 28, 2021
Solved

Sling model unit test with constructor

  • October 28, 2021
  • 1 reply
  • 4922 views

Hi everyone

I am having some trouble testing a Sling model, My Sling interface looks like this:

 

 

@Model(adaptables= Resource.class) public interface ImageFormItem { /** * @2007960 the title. */ @586265 String getTitle(); /** * @2007960 the url. */ @586265 String getUrl(); /** * @2007960 the image reference as a string. */ @586265 String getFileReference(); }

 

 

 

my sling model:

 

 

@Model(adaptables = Resource.class) public class ImageFormItemImpl implements ImageFormItem { private static final String PN_URL = "url"; private static final String PN_TITLE = "title"; private static final String PN_REFERENCE = "fileReference"; private SlingHttpServletRequest request; private Resource options; private ValueMap properties; @586265 public ImageFormItemImpl(){ } @586265 public ImageFormItemImpl(SlingHttpServletRequest request, Resource options, Resource option) { this.request = request; this.options = options; this.properties = option.getValueMap(); } @9944223 public String getTitle() { return properties.get(PN_TITLE, String.class); } @9944223 public String getUrl() { return properties.get(PN_URL, String.class); } @9944223 public String getFileReference() { return properties.get(PN_REFERENCE, String.class); } }

 

 

 

and my test class:

 

 

@ExtendWith({AemContextExtension.class, MockitoExtension.class}) class ImageFormItemImplTest { private final AemContext aemContext = new AemContext(ResourceResolverType.JCR_MOCK); @Mock ValueMap valueMap; @Mock private SlingHttpServletRequest request; @Mock private Resource options; @Mock private Resource properties; @Mock private ModelFactory modelFactory; @InjectMocks private ImageFormItemImpl imgFormItem = new ImageFormItemImpl(); @BeforeEach void setUp() { //registers the Sling Model to be tested, into the mock AEM Context, so it can be instantiated in the @2785667 methods. aemContext.addModelsForClasses(ImageFormItemImpl.class); //aemContext.registerService(ImageFormItemImpl.class,imgFormItem); //aemContext.addModelsForClasses(ImageFormItemImpl.class); //loads resource structures into the mock context, allowing the code to interact with these resources as if they were provided by a real repository. aemContext.load().json("/com/biomerieux/adobe/core/models/impl/imageformitem.json", "/content/imageform"); } @2785667 void getTitle() { final String expected = "Facebook"; //ctx.currentResource sets the context of the mock resource to evaluate the code against, aemContext.currentResource("/content/imageform/items/item1"); ImageFormItem imgFormItem = aemContext.request().adaptTo(ImageFormItem.class);; String actual = imgFormItem.getTitle(); assertEquals(expected, actual); } @2785667 void getUrl() { fail("Not yet implemented"); } @2785667 void getFileReference() { fail("Not yet implemented"); } }

 

 

imageformitem.json: 

 

 

{ "jcr:primaryType": "nt:unstructured", "jcr:createdBy": "admin", "jcr:lastModifiedBy": "admin", "source": "local", "jcr:created": "Fri Oct 29 2021 14:37:19 GMT+0200", "jcr:lastModified": "Fri Oct 29 2021 14:39:08 GMT+0200", "sling:resourceType": "onebmx/components/image-form", "items": { "jcr:primaryType": "nt:unstructured", "item0": {"jcr:primaryType": "nt:unstructured"}, "item1": { "jcr:primaryType": "nt:unstructured", "fileReference": "/content/dam/onebmx/5771789.png", "url": "https://www.youtube.com/", "title": "youtube" }, "item2": { "jcr:primaryType": "nt:unstructured", "fileReference": "/content/dam/onebmx/facebook.png", "url": "https://fr-fr.facebook.com/biomerieux/", "title": "facebook" } } }

 

 

and compiling my code I get an error:

 

 

java.lang.NullPointerException adobe.core.models.impl.ImageFormItemImplTest.getTitle(ImageFormItemImplTest.java:65)

 

 

Does anyone have any idea why i can't read the title in my json file, thanks in advance

 

This post is no longer active and is closed to new replies. Need help? Start a new post to ask your question.
Best answer by joerghoh

Can you change it

 

ImageFormItem imgFormItem = aemContext.resourceResolver().getResource("/content").adaptTo(ImageFormItem.class);

1 reply

joerghoh
Adobe Employee
Adobe Employee
October 29, 2021

You should revisit the usage of Mocks, specially using the @Mock annotation. The WCM Mock library you are using provides a lot of test implementations, so you don't need to mock ValueMaps etc.

 

Next, you are creating the imgFormItem when instantiating the class, but you load your content in the setup method. Meaning that the imgFormItem does know nothing about that content. 

 

So your test could look like that:

class ImageFormItemImplTest {

    public final AemContext aemContext = new AemContext();


    @BeforeEach
    void setUp() {
        //registers the Sling Model to be tested, into the mock AEM Context, so it can be instantiated in the  methods.
        aemContext.addModelsForClasses(ImageFormItemImpl.class);
        //loads resource structures into the mock context, allowing the code to interact with these resources as if they were provided by a real repository.
        aemContext.load().json("/com/biomerieux/adobe/core/models/impl/imageformitem.json", "/content");
    }

    
    void getTitle() {
        final String expected = "Facebook";
        //ctx.currentResource sets the context of the mock resource to evaluate the code against,
        // so this is set to /content/imageform as that is where the mock imageform content resource is loaded.
        aemContext.currentResource("/content");

        //instantiates the ImageFormItem Sling Model by adapting it from the mock Request object.
        ImageFormItem imgFormItem = aemContext.request().adaptTo(ImageFormItem.class);

        System.out.println("imgFormItem: "+imgFormItem);
        String actual =  imgFormItem.getTitle();
        assertEquals(expected, actual);
    }
    
    void getUrl() {
    }

    void getFileReference() {
    }
}

 

Next, when I was able to use AEM Context I never used Constructor injection, because it did not bring any value. Using standard injections will be sufficient.

 

 

odabioAuthor
October 29, 2021

Did as you told me but got a null pointer exception on this line :

String actual =  imgFormItem.getTitle();

 But the error starts in a line earlier, in this line

ImageFormItem imgFormItem = aemContext.request().adaptTo(ImageFormItem.class);

Here the variable imgFormItem is null for some reason it cannot instantiate the sling model.

joerghoh
Adobe Employee
Adobe Employee
October 31, 2021

Still getting the  null point exception event though i changed your the model as you told me.

 

but i need the custon constructor cuz it trought it that i can add new items


The constructor injection is just another way for the framework to instantiate your models. It's not more powerful than the "regular" way, but sometimes it can ease the testing if you can invoke the constructor with a number of mocks than relying on a framework like Sling Testing Mocks.

 

That means you can only inject parameters which you could otherwise inject. Plus you need to annotate the parameters as described in the Sling Models documentation [1]. In my opinion its benefits are quite limited.

 

 

[1] https://sling.apache.org/documentation/bundles/models.html (search for "constructor injection" as it is mentioned a number of locations)