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
Solved! Go to Solution.
Views
Replies
Total Likes
Can you change it
ImageFormItem imgFormItem = aemContext.resourceResolver().getResource("/content").adaptTo(ImageFormItem.class);
Views
Replies
Total Likes
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.
Views
Replies
Total Likes
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.
Views
Replies
Total Likes
Can you change it
ImageFormItem imgFormItem = aemContext.resourceResolver().getResource("/content").adaptTo(ImageFormItem.class);
Views
Replies
Total Likes
I getting the same nullpointexception
Views
Replies
Total Likes
Can you please update your initial posting and add the annotations you have on the sling model class?
yes, I've updated it
Views
Replies
Total Likes
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.
Views
Replies
Total Likes
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
Views
Replies
Total Likes
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)
Views
Replies
Total Likes
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);
Views
Replies
Total Likes
hi @Jörg_Hoh I am also facing the same issue but by doing the changes suggested by you I am still not able to resolve the issue and getting null value
I have shared my problem here please have a look
Views
Replies
Total Likes
Views
Likes
Replies
Views
Like
Replies
Views
Likes
Replies