Your achievements

Level 1

0% to

Level 2

Tip /
Sign in

Sign in to Community

to gain points, level up, and earn exciting badges like the new
Bedrock Mission!

Learn more

View all

Sign in to view all badges

SOLVED

Sling model unit test with constructor

odabio
Level 2
Level 2

Hi everyone

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

 

 

@Model(adaptables= Resource.class)
public interface ImageFormItem {

    /**
     * @Return the title.
     */
    @inject
    String getTitle();

    /**
     * @Return the url.
     */
    @inject
    String getUrl();

    /**
     * @Return the image reference as a string.
     */
    @inject
    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;

    @inject
    public ImageFormItemImpl(){   }

    @inject
    public ImageFormItemImpl(SlingHttpServletRequest request, Resource options, Resource option) {
        this.request = request;
        this.options = options;
        this.properties = option.getValueMap();
    }

    @Override
    public String getTitle() {
        return properties.get(PN_TITLE, String.class);
    }

    @Override
    public String getUrl() {
        return properties.get(PN_URL, String.class);
    }

    @Override
    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 @test 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");
    }

    @test
    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);
    }

    @test
    void getUrl() {
        fail("Not yet implemented");
    }

    @test
    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

 

1 Accepted Solution
Jörg_Hoh
Correct answer by
Employee
Employee

Can you change it

 

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

View solution in original post

10 Replies
Jörg_Hoh
Employee
Employee

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.

 

 

odabio
Level 2
Level 2

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.

Jörg_Hoh
Correct answer by
Employee
Employee

Can you change it

 

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

View solution in original post

Jörg_Hoh
Employee
Employee

Can you please update your initial posting and add the annotations you have on the sling model class?

Jörg_Hoh
Employee
Employee

Thanks!

The fact, that your annotation allows the model only to be adapted from a resource, the adaption via the request won't work. So you definitely need to use

 

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

Then I would change the complete model to something like this:

@Model(adaptables = Resource.class)
public class ImageFormItemImpl implements ImageFormItem {

    @inject
	private String title;
	
	@Inject
	private String url;
	
	@Inject String fileReference;

    @Override
    public String getTitle() {
        return title;
    }

    @Override
    public String getUrl() {
        return url;
    }

    @Override
    public String getFileReference() {
        return fileReference;
    }
}

which is a very simple Sling Model. Note, that I removed the custom constructor entirely, because it makes the situation just more complex.

 

 

 

odabio
Level 2
Level 2

ImageFormItemImplTest.png

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

mageFormItemImpl.java [onebmx.core].png

 

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

2021-10-31 00_17_33-onebmx-aem – ImageFormImpl.java [onebmx.core].png

Jörg_Hoh
Employee
Employee

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)

odabio
Level 2
Level 2

Thanks you @Jörg_Hoh It worked I wasn't specifying the right path to the ressource, now it works, I didn't add "imageform/items/item1" in the ressource.

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