Org Skills

aem-testing

Design and write tests for Adobe Experience Manager projects — JUnit 5 unit tests with AEM Mocks for Sling Models, servlets and OSGi services, integration tests with AEM testing clients, UI tests, front-end Jest tests, and Cloud Manager quality gates.

Download .zip Raw Source
When agents use itUse this whenever the user writes or fixes a test for AEM code, mentions AemContext, AemContextExtension, io.wcm aem-mock, ResourceResolverType (JCR_MOCK, RESOURCERESOLVER_MOCK, JCR_OAK), context.load().json, registerInjectActivateService, MockSlingHttpServletRequest, Mockito in AEM, the it.tests or ui.tests module, Cypress/WebdriverIO/Selenium in Cloud Manager, JaCoCo coverage, SonarQube or OakPAL failures, a pipeline blocked by the code quality gate, or asks how to test an HTL component, dialog or content policy. Also apply it when reviewing AEM test code or planning a test strategy for an AEM project.

Install

Copilot (VS Code, Visual Studio, Copilot CLI and github.com) reads skills from the repository — commit them so the whole team gets them.

npx skills add AGCO-Global/org-skills --skill aem-testing -a github-copilot
# or with the org installer (adds .github/skills/aem-testing):
npx -y github:AGCO-Global/org-skills add skill aem-testing

Uses the open skills CLI. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, OpenCode, Windsurf and 60+ others — it asks which agent to install into.

npx skills add AGCO-Global/org-skills --skill aem-testing
# user-level instead of project-level:
npx skills add AGCO-Global/org-skills --skill aem-testing -g

Installs the aem-quality-skills plugin, which bundles all AEM / Quality skills and keeps them updated.

/plugin marketplace add AGCO-Global/org-skills
/plugin install aem-quality-skills@org-skills
# or just this skill, in this repository:
npx skills add AGCO-Global/org-skills --skill aem-testing -a claude-code

Use Download .zip above, then upload it under Customize → Skills → Upload skill. Team and Enterprise admins can sync this repo as a plugin marketplace instead.

Installs into .agents/skills/, which Codex reads.

npx skills add AGCO-Global/org-skills --skill aem-testing -a codex

Installs into Cursor's skills folder.

npx skills add AGCO-Global/org-skills --skill aem-testing -a cursor

Installs into Gemini CLI's skills folder.

npx skills add AGCO-Global/org-skills --skill aem-testing -a gemini-cli

Any tool with rules, instructions or custom prompts: use Copy SKILL.md above and paste it in. It is plain Markdown.

Commands use your normal git sign-in to GitHub, so they work while the repository is private. Node.js 20+ required.

Skill contents

AEM Testing

Most AEM logic lives in Sling Models, services and servlets — test it there, fast, with AEM Mocks and realistic JSON content; reserve slow, flaky-prone browser and instance tests for what only a running AEM can prove. A test that needs a live instance to check a null-check is in the wrong layer.

1. What to test at which layer

Layer Tool Tests Runs
Unit (most tests) JUnit 5 + AEM Mocks + Mockito (core) Sling Models, services + configs, servlets, filters, job consumers, workflow steps Every build; Cloud Manager coverage
Front-end unit Jest (ui.frontend) JS/TS modules, client-side component logic Maven build via the front-end plugin
Content/package OakPAL, Cloud Manager rules, filter checks Package filters, /apps vs /content separation, index definitions Code quality step
Integration AEM testing clients (it.tests) Servlet endpoints, rendered component markup, service users/ACLs, replication Against a deployed environment
UI / E2E Cypress, WebdriverIO, Selenium, Playwright (ui.tests) Critical author and visitor journeys Cloud Manager custom UI testing
Performance JMeter / k6 / Gatling, Lighthouse Throughput through CDN/Dispatcher, page weight Stage, scheduled, not per commit

Push every assertion as far down this table as it can go.

2. AEM Mocks — choose the ResourceResolverType

Type Use when Trade-off
RESOURCERESOLVER_MOCK (default) Models, servlets, services using the Sling API only Fastest; no JCR Node/Session, no queries
JCR_MOCK Code adapts to Node/Session In-memory JCR; queries return only what you stub via MockJcr
JCR_OAK Real queries, node types, ACLs, versioning Needs the Oak mock dependency; seconds per context — isolate in few test classes
NONE No repository at all Pure OSGi wiring tests

Pick the lightest type that exercises the code path; switching everything to JCR_OAK to make one test pass hides design problems and slows the suite.

3. Testing Sling Models

@ExtendWith({AemContextExtension.class, MockitoExtension.class})
class TeaserModelTest {
    private final AemContext context = new AemContext();   // RESOURCERESOLVER_MOCK

    @BeforeEach
    void setUp() {
        context.addModelsForClasses(TeaserModel.class);
        context.load().json("/com/acme/core/models/TeaserModelTest.json", "/content/acme");
    }

    @Test
    void usesPageTitleWhenTitleNotAuthored() {
        context.currentResource("/content/acme/en/jcr:content/root/teaser-no-title");
        TeaserModel model = context.request().adaptTo(TeaserModel.class);
        assertNotNull(model);
        assertEquals("English Home", model.getTitle());
    }
}
  • One JSON fixture per test class, next to it in src/test/resources, containing one resource per scenario (authored, empty, partially authored, invalid). This is how you cover dialog fields: every dialog field should appear authored and missing somewhere in the fixture.
  • Content policies: context.contentPolicyMapping("acme/components/teaser", "showDescription", true) rather than hand-building /conf trees.
  • Adapt from the same adaptable as production (request vs resource); a model that works from a resource in tests can return null from the request in HTL.
  • Test the model's getters, not HTL. HTL rendering has no practical unit-test harness — keep logic out of HTL so there's nothing left to test there; verify markup in integration tests if it matters.
  • For Core Components delegation, also add the Core Components test dependency and register the delegate's models.

4. OSGi services with configuration

@Test
void appliesConfiguredTimeout() {
    context.registerService(HttpClientFactory.class, httpClientFactoryMock);
    PriceService service = context.registerInjectActivateService(new PriceServiceImpl(),
            Map.of("endpoint", "https://test.invalid", "timeout", 500));
    …
}
  • registerInjectActivateService runs real DS injection and @Activate; mandatory references must be registered first or it fails — that failure is a useful test of your wiring.
  • Mock outbound dependencies (HTTP clients, JobManager, Replicator) with Mockito and verify the interactions; use real AEM Mocks objects (resources, pages, ResourceResolver) instead of mocking them.
  • Service users: service logins through the mocked ResourceResolverFactory see the same mock repository — test the logic, and verify ACLs in integration tests.

5. Servlets, filters, jobs, workflow steps

context.currentResource("/content/acme/en/jcr:content/product");
context.requestPathInfo().setSelectorString("prices");
context.requestPathInfo().setExtension("json");
context.request().setQueryString("currency=EUR");
servlet.doGet(context.request(), context.response());
assertEquals(200, context.response().getStatus());
assertEquals("application/json", context.response().getContentType());
JsonNode body = new ObjectMapper().readTree(context.response().getOutputAsString());
  • Cover the error paths: bad selector/parameter → 400, missing resource → 404, downstream failure → 5xx with no stack trace in the body.
  • JobConsumer: build a mocked Job with properties, assert JobResult for success, retryable failure and permanent failure.
  • WorkflowProcess: mock WorkItem, WorkflowData (payload path) and MetaDataMap for process args; run against AEM Mocks content.
  • Filters: invoke doFilter with a Mockito FilterChain and verify it was (or wasn't) called.

6. Integration tests (it.tests)

  • Use the AEM testing clients (aem-cloud-testing-clients) with author/publish class rules; Cloud Manager runs this module in the custom functional testing step against the deployed environment.
  • Test what mocks cannot: servlet resolution and Dispatcher-facing URLs, ACLs and service users, repoinit results, replication to publish, rendered markup of key components.
  • Create test content under a unique, test-owned path and delete it in teardown; never depend on authored production content.
  • Poll with timeouts for async effects (replication, jobs) — no fixed sleeps.

7. UI tests (ui.tests) and front-end tests

  • Cloud Manager builds ui.tests into a Docker image and runs it against the environment, passing URLs and credentials as environment variables (e.g. AEM_AUTHOR_URL, AEM_PUBLISH_URL); read them, never hardcode hosts. The module must opt in (a testing.properties with ui-tests.version=1 in current archetypes) and publish reports where the pipeline expects them.
  • Keep the suite small: log in to author, author a component, publish, verify on publish; key visitor journeys. Use stable data-* selectors, not generated CSS classes.
  • ui.frontend: Jest (with jsdom) for component JS; run in the Maven build so failures break the pipeline.

8. Cloud Manager quality gates

  • Code coverage (JaCoCo) under the threshold (50 % on Cloud Service at time of writing) is an Important failure — the pipeline pauses for override. Treat it as a floor; aim for ~80 % on models/services, excluding generated or trivial classes explicitly in JaCoCo, not by writing assertion-free tests.
  • SonarQube reliability/security ratings and AEM-specific rules (e.g. admin sessions, resolvers not closed, Thread.sleep, deprecated APIs) can be Critical and fail the build — fix, don't suppress; any suppression carries a comment.
  • OakPAL/package rules: mixed mutable/immutable packages, /libs overlays of non-overlayable nodes, bad index definitions, config in wrong folders.
  • Reproduce locally: run the same Maven build, the AEM analyser plugin, and a SonarQube scan with the project's quality profile before pushing.

9. Deterministic tests and fixtures

  • Inject a Clock (or time provider) into services; never assert on new Date(). Fix Locale and TimeZone in tests that format.
  • No network, no real instance, no Thread.sleep in unit tests; no order dependence — a fresh AemContext per test (the extension does this).
  • Fixture JSON is minimal and readable: only properties the code reads, realistic sling:resourceType values.
  • Performance: script load tests against stage through the CDN, with realistic cache-hit ratios; coordinate before running, and compare against a baseline.

Deliverable format

  1. Layer decision: which layer each behaviour is tested at, and why not lower.
  2. Test classes with JUnit 5 + AEM Mocks, named <ClassUnderTest>Test, one scenario per test method, descriptive names.
  3. Fixtures: JSON content files and policy mappings, covering authored/empty/invalid dialog states.
  4. Integration/UI tests only for flows that need a running AEM, with setup/teardown of test content.
  5. Quality gate notes: coverage impact, Sonar/OakPAL rules touched, JaCoCo exclusions justified.

Anti-patterns to reject

  • Mocking Resource, ValueMap or ResourceResolver with Mockito instead of loading JSON into AEM Mocks.
  • JCR_OAK as the default for every test class; slow suites nobody runs locally.
  • Tests without assertions, or asserting only assertNotNull(model), written to pass the coverage gate.
  • Business logic in HTL or client-side JS "because the model is hard to test".
  • UI tests that check what a unit test could; Thread.sleep, hardcoded hosts or credentials in ui.tests/it.tests.
  • Integration tests depending on existing authored content or leaving test content behind.
  • Suppressing SonarQube or AEM rules without a written reason; lowering coverage by excluding real code.
  • Time-, locale- or order-dependent tests that fail intermittently in the pipeline.