Expand my Community achievements bar.

Enhance your AEM Assets & Boost Your Development: [AEM Gems | June 19, 2024] Improving the Developer Experience with New APIs and Events
SOLVED

unable to mock Sling model object which is annotated with a custom annotation

Avatar

Level 1

Hi everyone ,
I am facing issue in mocking a sling model object which have custom annotation.

this is my model class for which i am writing test class : -

 

@Model(adaptables = {Resource.class, SlingHttpServletRequest.class}, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class DownloadLinkModel {

@inject
private AuthorizationService authorizationService;

@inject
private Resource resource;

@inject @Via("resource")
private String listType

@inject @Via("resource")
private String parentPath;

@inject @Via("resource")
private String[] tags;

@inject @Via("resource")
private String openNewWindow;

@inject @Via("resource")
private int maxLimitToDisplay;

@inject @Via("resource")
private String enableTeaserContent;

@inject @Via("resource")
private String sortBy;

@inject @Via("resource")
private int maxLimitInEachCol;

@inject @Via("resource")
private String downloadLinksheader;

@inject @Via("resource")
private Resource download;

@inject @Via("resource")
private String enableInnerGrid;

@inject
private Page resourcePage;

@inject @ScriptVariable
private Page currentPage;

@inject @Via("resource")
private Resource res;

@inject @source("sling-object")
private SlingHttpServletRequest slingRequest;

@inject @source("sling-object")
private ResourceResolver resourceResolver;

@MySiteConfigInjector
private Optional<SiteSlingModel> siteSlingModel;              // NULL

private boolean isDateFormat = false;

@PostConstruct
protected void init() {

if (siteSlingModel.isPresent()) {                      // getting null pointer exception here
isDateFormat = Boolean.parseBoolean(siteSlingModel.get().getDateFormat());
}
if (null != resourcePage){
switch(getListType()) {
            // BUSINESS LOGIC
                          }
}
}
}



this is my test class :-
 
@ExtendWith(value = {AemContextExtension.class})
class DownloadLinkModelTest {

private static final String TEST_PATH = "/content/en/page/jcr:content/root/responsivegrid";


public final AemContext context = new AemContext();

private DownloadLinkModel downloadLinkModel;

@Mock
private Resource resource;

@Mock
private SiteSlingModel siteSlingModel;

@BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);

context.addModelsForClasses(DownloadLinkModel.class);
context.create().page("/content/en");
Page page = context.create().page("/content/en/page");
context.currentPage(page);
context.load().json("downloadlink.json", "/resource");
resource = context.currentResource("/resource/downloadlink-tags");
context.request().setResource(resource);
downloadLinkModel = context.request().adaptTo(DownloadLinkModel.class);    // NULL
}

@test
@displayname("Test getDownloadLink - Tags List")
public void testGetDownloadLinkTagsList() {

assertNotNull(downloadLinkModel);    // test failed 
assertEquals("tags", downloadLinkModel.getListType());
assertEquals("title", downloadLinkModel.getSortBy());

}
 
 
 
downloadlink.json file
 
 
{
"downloadlink-tags": {
"jcr:primaryType": "nt:unstructured",
"jcr:createdBy": "b994430",
"jcr:lastModifiedBy": "a047776",
"sortBy": "title",
"openNewWindow": "true",
"jcr:created": "Thu May 11 2023 06:18:03 GMT-0400",
"tags": [
"tag1"
],
"jcr:lastModified": "Wed Sep 13 2023 01:48:04 GMT-0400",
"sling:resourceType": "myProject/components/general/download-link",
"listType": "tags",
"maxLimitToDisplay": "40"
}
}
 
 
 
does anyone have any idea how to mock an instance of Optional<SiteSlingModel> which is annotated with a custom annotation
 
Thanks in advance.
 
 
1 Accepted Solution

Avatar

Correct answer by
Level 9

Hi @varunkiroriwal ,

To properly mock the Optional<SiteSlingModel> and ensure it's injected into your DownloadLinkModel, you need to ensure the custom injector is correctly set up and registered in the AEM context. The approach involves a few key steps: ensuring the injector is recognized, registering your mocks appropriately, and verifying the custom annotation behaves as expected.

Here's a more detailed and structured approach:

1. Custom Injector Implementation

Ensure your custom injector implementation is correctly defined and registers the SiteSlingModel appropriately. If MySiteConfigInjectorImpl isn't handling the injection properly, SiteSlingModel won't be injected.

2. Ensure Proper Context and Model Registration

Make sure that your DownloadLinkModel and any other necessary models are correctly registered in the AEM context.

3. Mocking and Injection Setup

Ensure that the mock for SiteSlingModel is registered correctly and that the custom injector is appropriately used.

Here’s a complete example:

Custom Injector Implementation

Ensure your injector correctly injects the SiteSlingModel based on your custom annotation.

 

public class MySiteConfigInjectorImpl implements Injector {
    public static final String MY_SITE_CONFIG_INJECT = "mysiteconfiginject";

    @Override
    public String getName() {
        return MY_SITE_CONFIG_INJECT;
    }

    @Override
    public Object getValue(Object adaptable, String name, Type type, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry) {
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            if (parameterizedType.getRawType().equals(Optional.class) &&
                parameterizedType.getActualTypeArguments()[0].equals(SiteSlingModel.class)) {
                // Custom logic to provide the SiteSlingModel instance
                return Optional.of(new SiteSlingModel()); // Replace with actual logic
            }
        }
        return null;
    }
}

 

Test Class

Here's your test class with some adjustments:

 

@ExtendWith(AemContextExtension.class)
class DownloadLinkModelTest {

    private static final String TEST_PATH = "/content/en/page/jcr:content/root/responsivegrid";

    public final AemContext context = new AemContext();

    private DownloadLinkModel downloadLinkModel;

    @Mock
    private Resource resource;

    @Mock
    private SiteSlingModel siteSlingModel;

    @Spy
    private MySiteConfigInjectorImpl mySiteConfigInjector;

    @BeforeEach
    public void setup() {
        MockitoAnnotations.initMocks(this);

        context.addModelsForClasses(DownloadLinkModel.class);
        context.create().page("/content/en");
        Page page = context.create().page("/content/en/page");
        context.currentPage(page);
        context.load().json("downloadlink.json", "/resource");
        resource = context.currentResource("/resource/downloadlink-tags");
        context.request().setResource(resource);

        // Register the custom injector
        context.registerService(Injector.class, mySiteConfigInjector);

        // Register the adapter
        context.registerAdapter(SlingHttpServletRequest.class, SiteSlingModel.class, siteSlingModel);

        // Mock the SiteSlingModel method
        when(siteSlingModel.getDateFormat()).thenReturn("true");

        // Adapt the request to the model
        downloadLinkModel = context.request().adaptTo(DownloadLinkModel.class);
    }

    @Test
    @DisplayName("Test getDownloadLink - Tags List")
    public void testGetDownloadLinkTagsList() {
        assertNotNull(downloadLinkModel);
        assertEquals("tags", downloadLinkModel.getListType());
        assertEquals("title", downloadLinkModel.getSortBy());
    }
}

 

  1. Register the Custom Injector: Ensure that your custom injector is registered in the AEM context (context.registerService(Injector.class, mySiteConfigInjector);).

  2. Mock the SiteSlingModel: Create the mock for SiteSlingModel and set up its behavior (when(siteSlingModel.getDateFormat()).thenReturn("true");).

  3. Adapt the Request to the Model: Ensure the request is adapted to the DownloadLinkModel after setting up all mocks and services.

  4. Custom Annotation Handling: Make sure the custom annotation is properly recognized and handled by your custom injector.

By following these steps, you should be able to properly mock and inject the Optional<SiteSlingModel> into your DownloadLinkModel and avoid the NullPointerException.

View solution in original post

4 Replies

Avatar

Level 9

Hi @varunkiroriwal ,

To mock an instance of Optional<SiteSlingModel> which is annotated with a custom annotation, you can use the @Mock annotation from Mockito and create a mock object for SiteSlingModel. Here's an example of how you can modify your test class to mock the siteSlingModel:

 

@ExtendWith(value = {AemContextExtension.class})
class DownloadLinkModelTest {

    private static final String TEST_PATH = "/content/en/page/jcr:content/root/responsivegrid";

    public final AemContext context = new AemContext();

    private DownloadLinkModel downloadLinkModel;

    @Mock
    private Resource resource;

    @Mock
    private SiteSlingModel siteSlingModel;

    @BeforeEach
    public void setup() {
        MockitoAnnotations.initMocks(this);

        context.addModelsForClasses(DownloadLinkModel.class);
        context.create().page("/content/en");
        Page page = context.create().page("/content/en/page");
        context.currentPage(page);
        context.load().json("downloadlink.json", "/resource");
        resource = context.currentResource("/resource/downloadlink-tags");
        context.request().setResource(resource);

        // Mock the siteSlingModel
        when(siteSlingModel.getDateFormat()).thenReturn("true");

        downloadLinkModel = context.request().adaptTo(DownloadLinkModel.class);
    }

    @Test
    @DisplayName("Test getDownloadLink - Tags List")
    public void testGetDownloadLinkTagsList() {
        assertNotNull(downloadLinkModel);
        assertEquals("tags", downloadLinkModel.getListType());
        assertEquals("title", downloadLinkModel.getSortBy());
    }
}

 

In the setup() method, after initializing the mocks with MockitoAnnotations.initMocks(this), you can use when(siteSlingModel.getDateFormat()).thenReturn("true") to mock the behavior of the getDateFormat() method of SiteSlingModel and return the desired value.

Make sure that the SiteSlingModel mock is properly injected into the DownloadLinkModel during the test setup. This should resolve the NullPointerException you were encountering when accessing siteSlingModel.isPresent().

Note: If the @MySiteConfigInjector annotation has additional behavior or dependencies, you may need to mock those as well to ensure the proper functioning of the test.

Avatar

Level 1

Hi @HrishikeshKa 
thanks for the response.

i tried as you said but still getting null pointer exception in same line.
declared custom annotation source class:-

@Spy
private MySiteConfigInjectorImpl mySiteConfigInjector;


I added these lines into my setup method:-

context.registerAdapter(SlingHttpServletRequest.class, SiteSlingModel.class,this.siteSlingModel);
context.registerService(Injector.class, mySiteConfigInjector);
when(siteSlingModel.getDateFormat()).thenReturn("false");

 

This is the Custom Annotation class:-

 

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@InjectAnnotation
@Source(MySiteConfigInjectorImpl.MY_SITE_CONFIG_INJECT)
public @interface MySiteConfigInjector {
InjectionStrategy injectionStrategy() default InjectionStrategy.DEFAULT;
}

 

Still SiteSlingModel mock is not getting injected properly into the DownloadLinkModel.

 

Avatar

Correct answer by
Level 9

Hi @varunkiroriwal ,

To properly mock the Optional<SiteSlingModel> and ensure it's injected into your DownloadLinkModel, you need to ensure the custom injector is correctly set up and registered in the AEM context. The approach involves a few key steps: ensuring the injector is recognized, registering your mocks appropriately, and verifying the custom annotation behaves as expected.

Here's a more detailed and structured approach:

1. Custom Injector Implementation

Ensure your custom injector implementation is correctly defined and registers the SiteSlingModel appropriately. If MySiteConfigInjectorImpl isn't handling the injection properly, SiteSlingModel won't be injected.

2. Ensure Proper Context and Model Registration

Make sure that your DownloadLinkModel and any other necessary models are correctly registered in the AEM context.

3. Mocking and Injection Setup

Ensure that the mock for SiteSlingModel is registered correctly and that the custom injector is appropriately used.

Here’s a complete example:

Custom Injector Implementation

Ensure your injector correctly injects the SiteSlingModel based on your custom annotation.

 

public class MySiteConfigInjectorImpl implements Injector {
    public static final String MY_SITE_CONFIG_INJECT = "mysiteconfiginject";

    @Override
    public String getName() {
        return MY_SITE_CONFIG_INJECT;
    }

    @Override
    public Object getValue(Object adaptable, String name, Type type, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry) {
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            if (parameterizedType.getRawType().equals(Optional.class) &&
                parameterizedType.getActualTypeArguments()[0].equals(SiteSlingModel.class)) {
                // Custom logic to provide the SiteSlingModel instance
                return Optional.of(new SiteSlingModel()); // Replace with actual logic
            }
        }
        return null;
    }
}

 

Test Class

Here's your test class with some adjustments:

 

@ExtendWith(AemContextExtension.class)
class DownloadLinkModelTest {

    private static final String TEST_PATH = "/content/en/page/jcr:content/root/responsivegrid";

    public final AemContext context = new AemContext();

    private DownloadLinkModel downloadLinkModel;

    @Mock
    private Resource resource;

    @Mock
    private SiteSlingModel siteSlingModel;

    @Spy
    private MySiteConfigInjectorImpl mySiteConfigInjector;

    @BeforeEach
    public void setup() {
        MockitoAnnotations.initMocks(this);

        context.addModelsForClasses(DownloadLinkModel.class);
        context.create().page("/content/en");
        Page page = context.create().page("/content/en/page");
        context.currentPage(page);
        context.load().json("downloadlink.json", "/resource");
        resource = context.currentResource("/resource/downloadlink-tags");
        context.request().setResource(resource);

        // Register the custom injector
        context.registerService(Injector.class, mySiteConfigInjector);

        // Register the adapter
        context.registerAdapter(SlingHttpServletRequest.class, SiteSlingModel.class, siteSlingModel);

        // Mock the SiteSlingModel method
        when(siteSlingModel.getDateFormat()).thenReturn("true");

        // Adapt the request to the model
        downloadLinkModel = context.request().adaptTo(DownloadLinkModel.class);
    }

    @Test
    @DisplayName("Test getDownloadLink - Tags List")
    public void testGetDownloadLinkTagsList() {
        assertNotNull(downloadLinkModel);
        assertEquals("tags", downloadLinkModel.getListType());
        assertEquals("title", downloadLinkModel.getSortBy());
    }
}

 

  1. Register the Custom Injector: Ensure that your custom injector is registered in the AEM context (context.registerService(Injector.class, mySiteConfigInjector);).

  2. Mock the SiteSlingModel: Create the mock for SiteSlingModel and set up its behavior (when(siteSlingModel.getDateFormat()).thenReturn("true");).

  3. Adapt the Request to the Model: Ensure the request is adapted to the DownloadLinkModel after setting up all mocks and services.

  4. Custom Annotation Handling: Make sure the custom annotation is properly recognized and handled by your custom injector.

By following these steps, you should be able to properly mock and inject the Optional<SiteSlingModel> into your DownloadLinkModel and avoid the NullPointerException.

Avatar

Administrator

@varunkiroriwal Did you find the suggestion helpful? Please let us know if you require more information. Otherwise, please mark the answer as correct for posterity. If you've discovered a solution yourself, we would appreciate it if you could share it with the community. Thank you!



Kautuk Sahni