Testing
Testing in AEM usually combines unit tests for backend logic, integration tests for content and services, and UI tests for authoring behavior.
Test pyramid (AEM)
- Unit tests: Fast, isolated tests (models, services, utilities).
- Integration tests: Repository + OSGi wiring in a test context.
- UI tests: Authoring and end-to-end flows in a browser.
What to cover
- Unit tests: Sling Models, services, and utilities.
- Integration tests: Repository interactions and OSGi configs.
- UI tests: Authoring flows and component dialogs.
Tools and frameworks
- JUnit 5 for Java tests.
- AEM Mocks for unit and integration-style tests.
- Sling Testing utilities for request/response helpers.
- Playwright/Cypress for authoring and UI flows.
Example: Sling Model unit test
@ExtendWith(AemContextExtension.class)
class TitleModelTest {
private final AemContext ctx = new AemContext();
@BeforeEach
void setUp() {
ctx.addModelsForClasses(TitleModel.class);
ctx.create().resource("/content/page/jcr:content",
"jcr:title", "Hello",
"sling:resourceType", "myproject/components/title");
}
@Test
void readsTitle() {
Resource resource = ctx.resourceResolver().getResource("/content/page/jcr:content");
TitleModel model = resource.adaptTo(TitleModel.class);
assertEquals("Hello", model.getTitle());
}
}
Example: Service test with OSGi config
@ExtendWith(AemContextExtension.class)
class FeatureServiceTest {
private final AemContext ctx = new AemContext();
@BeforeEach
void setUp() {
ctx.registerInjectActivateService(new FeatureServiceImpl(),
"enabled", true,
"threshold", 10);
}
@Test
void featureIsEnabled() {
FeatureService service = ctx.getService(FeatureService.class);
assertTrue(service.isEnabled());
}
}
Example: Integration-style repository test
@ExtendWith(AemContextExtension.class)
class ContentQueryTest {
private final AemContext ctx = new AemContext();
@BeforeEach
void setUp() {
ctx.create().resource("/content/site/en",
"jcr:title", "Home",
"sling:resourceType", "myproject/components/page");
}
@Test
void findByPath() {
ResourceResolver resolver = ctx.resourceResolver();
Resource resource = resolver.resolve("/content/site/en");
assertFalse(ResourceUtil.isNonExistingResource(resource));
}
}
Example: Load JSON content for Sling Models
You can load JSON content into the AemContext from your test resources folder and adapt the resource to a model.
@ExtendWith(AemContextExtension.class)
class TeaserModelTest {
private final AemContext ctx = new AemContext();
@BeforeEach
void setUp() {
ctx.addModelsForClasses(TeaserModel.class);
ctx.load().json("/content/teaser.json", "/content/site/en");
}
@Test
void readsTeaserProperties() {
Resource resource = ctx.resourceResolver().getResource("/content/site/en/jcr:content/root/teaser");
TeaserModel model = resource.adaptTo(TeaserModel.class);
assertEquals("My Teaser", model.getTitle());
assertEquals("/content/site/en/page", model.getLink());
}
}
Example JSON file in test resources:
src/test/resources/content/teaser.json
{
"jcr:primaryType": "cq:Page",
"jcr:content": {
"jcr:primaryType": "cq:PageContent",
"root": {
"jcr:primaryType": "nt:unstructured",
"teaser": {
"jcr:primaryType": "nt:unstructured",
"sling:resourceType": "myproject/components/teaser",
"title": "My Teaser",
"link": "/content/site/en/page"
}
}
}
}
UI testing guidance
- Keep UI tests minimal and focused on critical authoring flows.
- Prefer stable selectors (data attributes) over text-based selectors.
- Run UI tests against a predictable environment (local SDK or dedicated test env).
Smoke test checklist
- Create a page with core components and custom components.
- Open each dialog and verify defaults and validations.
- Publish and verify rendering on publish.
- Check 404s and clientlib loading.
CI tips
- Run unit tests on every commit.
- Run integration tests nightly or on main merges.
- Keep UI tests in a separate stage to avoid slowing down PR checks.
Practical tips
- Prefer fast unit tests for business logic.
- Keep integration tests focused on content structure and permissions.
- Smoke-test authoring scenarios before releases.