Expand my Community achievements bar.

SOLVED

Unit Testing ChildResource

Avatar

Community Advisor

In Unit Test, @ChildResource annotation property always return null. Can anyone please guide me to resolve the issue?

Environment Details: AEM 6.5.16 and Java 11

CountryListImpl.java

 

@Model(
    adaptables = SlingHttpServletRequest.class,
    adapters = { CountryList.class },
    resourceType = CountryListImpl.RESOURCE_TYPE,
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(
    name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
    extensions = ExporterConstants.SLING_MODEL_EXTENSION
)
public class CountryListImpl implements CountryList {
    protected static final String RESOURCE_TYPE = "aem-demo/components/country-list";
    protected static final String COUNTRY_LANGUAGE_LIST = "countryItems";

    @ValueMapValue
    @Named("title")
    String defaultLabel;

    @ChildResource(name = COUNTRY_LANGUAGE_LIST)
    List< CountryItem > countryList;

    public String getDefaultLabel() {
        return defaultLabel;
    }

    public List<CountryItem> getCountryList() {
        return countryList;
    }
}

 

CountryItemImpl.java

 

@Model(
    adaptables = Resource.class,
    adapters = { CountryItem.class },
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class CountryItemImpl implements CountryItem {
    @ValueMapValue
    String country;

    @ValueMapValue
    String language;

    @ValueMapValue
    String link;

    public String getCountry() {
        return country;
    }

    public String getLanguage() {
        return language;
    }

    public String getLink() {
        return link;
    }
}

 

CountryListImplTest.java

 

@ExtendWith(AemContextExtension.class)
public class CountryListImplTest {
    private final AemContext ctx = new AemContext(ResourceResolverType.JCR_MOCK);

    private CountryList countryList;

    @BeforeEach
    public void setup() {
        ctx.load().json(
            "/com/aem/demo/core/components/internal/models/v1/country-list.json",
            "/content"
        );

        ctx.currentResource("/content/country-list");
        countryList = Objects.requireNonNull(ctx.getService(ModelFactory.class))
            .createModel(ctx.request(), CountryListImpl.class);
    }

    
    public void getCountryListTest() {
        Assertions.assertNotNull(countryList);
//      countryList.getCountryList() --> ALWAYS RETURN NULL
//      Assertions.assertNotNull(countryList.getCountryList());
//      Assertions.assertEquals(
//        2, countryList.getCountryList().size()
//      );
    }
}

 

country-list.json

 

{
  "country-list": {
    "jcr:primaryType": "nt:unstructured",
    "sling:resourceType": "awcm-demo/components/country-list",
    "title": "Select ...",
    "countryItems": {
      "jcr:primaryType": "nt:unstructured",
      "item0": {
        "jcr:primaryType": "nt:unstructured",
        "country": "Austria",
        "link": "/content/aem-demo/at/de",
        "language": "German"
      },
      "item1": {
        "jcr:primaryType": "nt:unstructured",
        "country": "Canada",
        "link": "/content/aem-demo/ca/en",
        "language": "English"
      }
    }
  }
}

 

1 Accepted Solution

Avatar

Correct answer by
Community Advisor

I've identified a simplified solution, and I only need to implement two modifications.

1. CountryListImpl:  change adaptables to Resource.Class

@Model(
  adaptables = Resource.class,
  adapters = { CountryLinkButton.class },
  resourceType = CountryLinkButtonImpl.RESOURCE_TYPE,
  defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(
  name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
  extensions = ExporterConstants.SLING_MODEL_EXTENSION
)
public class CountryLinkButtonImpl implements CountryLinkButton {
.....
}

2. CountryListImplTest: Update createModel

createModel(ctx.request().getResource(), CountryListImpl.class);

Reference: https://medium.com/@veena.vikraman19/aem-junit-for-simple-aem-component-with-multifield-2326f61c8982 

View solution in original post

8 Replies

Avatar

Employee Advisor

Hi,

 

In the given test case, the @ChildResource annotation may return null because the AEM context used for testing is not properly initialized to include the child resources from the JSON file. To resolve this issue, you can modify the test setup method as follows:

@BeforeEach
public void setup() {
ctx.load().json(
"/com/aem/demo/core/components/internal/models/v1/country-list.json",
"/content"
);

// Update the path to include the child resource
ctx.currentResource("/content/country-list/countryItems");

countryList = Objects.requireNonNull(ctx.getService(ModelFactory.class))
.createModel(ctx.request(), CountryListImpl.class);
}

 

-> By updating the path to include /countryItems, you ensure that the child resource is loaded correctly, and the @ChildResource annotation can populate the countryList property.

Please note that the JSON file and the AEM context setup should match the structure and resource paths in your project for the test to run successfully.

Avatar

Community Advisor

Hi @ManviSharma, I updated the current resource to /content/country-list/countryItems but countryList.getCountryList() still NULL. Thanks.

Avatar

Employee

You are using Model class with name (adaptor class name ) CountryList.class and in createModel you are using CountryListImpl.class  please correct  - 

 

 

 

 

countryList = Objects.requireNonNull(ctx.getService(ModelFactory.class)) .createModel(ctx.request(), CountryListImpl.class);

 

 

to 

 

 

        countryList = Objects.requireNonNull(ctx.getService(ModelFactory.class))
            .createModel(ctx.request(), CountryList.class);

 

 

 

Avatar

Community Advisor

Hi @Nishant-Singh, countryList is an object of CountryListImpl (not CountryItem), CountryItem will response ClassCastException.

Avatar

Employee

you need to create object  by adapting method

countyList = aemContext.request().adaptTo(CountryList.class);

 

Avatar

Community Advisor

Hi @Mahedi_Sabuj ,

Just checked your problem. You are using Sling Deligation Pattern, I have an article on how to write unit tests for this type of Sling Model: https://medium.com/@sadyrifat/aem-unit-test-case-for-sling-delegation-pattern-1f011b947fb3 

The way you are approaching is somehow wrong and you will get trouble on this.

 

By using this article your Unit test will be like this: 

 

 

private final AemContext context = new AemContextBuilder(ResourceResolverType.JCR_MOCK).plugin(CORE_COMPONENTS).build();
    private CountryList countryList;

    @BeforeEach
    void init() {
        Resource pageResource = context.load().json("/com/aem-demo/core/internal/models/page.json", "/content/uk");
        Page page = pageResource.getChild("en/product").adaptTo(Page.class);

        Map<String, Object> jsonObject = TestUtils.getJsonObject("/com/aem-demo/core/internal/models/country-list.json");
        context.currentResource(context.create().resource(page, "countryList", jsonObject));
        countryList = context.request().adaptTo(CountryListImpl.class);
    }

    @Test
    void countryList() {
        assertNotNull(countryList);
    }

 

And most importantly your json will be this,

 

{
    "jcr:primaryType": "nt:unstructured",
    "sling:resourceType": "awc-demo/components/country-list",
    "title": "Select ...",
    "countryItems": {
        "jcr:primaryType": "nt:unstructured",
        "item0": {
            "jcr:primaryType": "nt:unstructured",
            "country": "Austria",
            "link": "/content/aem-demo/at/de",
            "language": "German"
        },
        "item1": {
            "jcr:primaryType": "nt:unstructured",
            "country": "Canada",
            "link": "/content/aem-demo/ca/en",
            "language": "English"
        }
    }
}

 

 

After running your code with this setup I am getting the clildresources.

Sady_Rifat_0-1689594195027.png

Hope this helps you.

Avatar

Community Advisor

Hi @Sady_Rifat, In Sling Model countryList return value as expected, issue is on Unit Test. Let me check your implementation, I will get back to you soon.

Avatar

Correct answer by
Community Advisor

I've identified a simplified solution, and I only need to implement two modifications.

1. CountryListImpl:  change adaptables to Resource.Class

@Model(
  adaptables = Resource.class,
  adapters = { CountryLinkButton.class },
  resourceType = CountryLinkButtonImpl.RESOURCE_TYPE,
  defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(
  name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
  extensions = ExporterConstants.SLING_MODEL_EXTENSION
)
public class CountryLinkButtonImpl implements CountryLinkButton {
.....
}

2. CountryListImplTest: Update createModel

createModel(ctx.request().getResource(), CountryListImpl.class);

Reference: https://medium.com/@veena.vikraman19/aem-junit-for-simple-aem-component-with-multifield-2326f61c8982