Unit testing in AEM - Debugging issues in Test class


This post is for illustrating the possible errors/exception we get while writing Test class.
Also highlights the possible ways of creating Test content and the ResourceResolverType available for creating AemContext (from io.wcm framework)

Test content :
  • Most important aspect of Unit Testing is creating Test content which in AEM sense is to provide a means of repository at the time of code build phase to test our Java code (written interacting with repo)
  • For creating the same, we already saw that there are 3 possible APIs - Content Loader, Content Builder and Resource Builder available.
  • Most often we use Content Loader making use of JSON file at class path and loading it to dummy content path. Below are the sample snippet making use of other two APIs.
  • Content Builder :
    • private final AemContext aemContext = new AemContext(ResourceResolverType.JCR_MOCK);
    • ContentBuilder contentBuilder = aemContext.create();
    • With this contentBuilder object, we can create page, asset, tag etc - Full list of methods available with ContentBuilder API is in doc.
  • Resource Builder:
    • private final AemContext aemContext = new AemContext(ResourceResolverType.JCR_MOCK);
    • ResourceBuilder rescBuilder= aemContext.build();
    • With this rescBuilder object, we can create resource with properties - Full list of methods available with ResourceBuilder API is in doc.
Resource Resolver Types  (as available from io.wcm)
  • RESOURCERESOLVER_MOCK - new AemContext(ResourceResolverType.RESOURCERESOLVER_MOCK);
    • Mock for Resource Resolver / Factory
    • Access to all Sling API 
    • No underlying JCR repository and hence usage of JCR API is not possible.
      •  (Example: Adapting to JCR objects will return null)
    • This type is very fast
    • Usage : If no JCR APIs are involved, we can make use of RESOURCERESOLVER_MOCK while creating AemContext
  • JCR_MOCK - new AemContext(ResourceResolverType.JCR_MOCK);
    • JCR In-memory mock as underlying repository. 
    • Support most of the JCR features with an exception of extended features like Eventing, Search, Versioning etc.
    • This is slow compared to RESOURCERESOLVER_MOCK 
    • Usage : If both Sling API and JCR APIs are involved, make use of JCR_MOCK. 
  • JCR_OAK - new AemContext(ResourceResolverType.JCR_OAK);
    • Uses a real JCR Jackrabbit Oak repository.
    • This is slow compared to JCR_MOCK
    • Usage : Common use case would be for logic involving JCR query, JCR Observation
  • Note : We can create AemContext without specifying ResourceResolverType as well - new AemContext()
Debugging/Troubleshooting:
  • Null pointer Exception:
    • Lets consider we are writing Test class for Sling Model and in Test class, we have created AemContext, mock content and instantiated the Sling model (via request or resource of aemContext)
    • In case if any of the APIs/objects used in sling model in -> init method / in constructor injected scenario is not available to Test class, it will result in Sling Model object being null. 
    • If null check if handled, it will result in next possible scenario. 
    • Example :
      • In the example below, "currentPageObj" in "init" method is an instance variable and is initialized in Injected Constructor. 
      • If currentPage object in the constructor (to initialize the "currentPageObj") is not set to AemContext, then currentPage in the constructor is null while the Sling model is instantiated in Test class and hence null for currentPageObj in init method -> Ultimately Sling Model object will be null in Test class.
      • In the Test class snippet below, we have used ContentBuilder to create Page object and is set to AemContext and then Sling Model is instantiated via request from AemContext. By this way, valid object is created and will be available to access its methods.
  • Assertion Failed : 
    • If the assertion conditions are not satisfied, then it will result in AssertionFailedError.
    • From the literal sense, we know if expected and actual are not the same, then it results in this sort of error. 
    • Most often we get this in below situation. 
    • Example :
      • In the same example as above in a Test class -> assume Page object is not set to AemContext -> Sling Model object retrieved -> null check is handled while invoking its method - In this case, it is getTitle() method, it would result in AssertionFailed error. (Expected would be some String while the actual title we get is null)
  • Mocking the right resource:
    • If we are to create mock resource JSON for performing tests, we need to understand which line of code to be tested and respective resource can be retrieved from actual content (from repo/CRXDE) using selector - "tidy.-1.json" on the respective resource. 
    • By the term "respective resource", 
      • It means node/resource of type cq:Page if we are to test page related functionality. 
      • It means node/resource of type nt:unstructured/component node under content path, if we are to test component related logic via ValueMap.
Conclusion:
  • First immediate reach is AemContext from io.wcm framework, if anything is not accessible within that, explore Mocking related frameworks and its methods -> provide dummy implementation per the code statements.
  • With the understanding on the flow of how test class works + making use of available Testing/Mocking APIs + method under test + functionality it executes + control flow when a specific line is executed, we will be able to write test class with ease and debug in case of any issues in test class execution. 
Other posts related to Unit Testing in AEM :

Comments

  1. Unit testing is one of the software testing types which includes the initial testing phase where the smallest components or the modules of a software are tested individually. With this method of testing, both testers and developers can isolate each module, identify and fix the system defects at a very early stage of the software development lifecycle (SDLC). Primarily, a unit test verifies different behavioral aspects of the system under test and can be broadly classified into state-based and interaction-based unit testing.

    ReplyDelete

Post a Comment

Popular posts from this blog

Embedding Third party dependency/OSGi bundle in AEM application hosted in AEMasCS

OSGI Factory Configuration implementation

Embed Third party dependency using bnd-maven-plugin