AEM content validation with JUnit
Reliable Adobe Experience Manager projects depend on more than compiling Java code and rendering a page in a browser. Content structures, component dialogs, Sling Models, templates, and delivery APIs all need predictable behavior as authors and developers change the implementation. JUnit provides a practical way to verify those expectations before a deployment reaches an AEM environment.
Content validation can mean several things in an AEM project. A test may check that a required property is present, a multifield produces the expected items, a content fragment exposes a valid model, or a Sling Model transforms repository data correctly. The most useful test suites define these rules clearly and exercise them with small, repeatable fixtures.
A strong approach combines unit testing, AEM-specific mock contexts, repository fixtures, and a limited number of integration tests. This keeps feedback fast while still covering the behavior that matters to content authors, frontend developers, and downstream consumers.
Define what valid content means
Before writing a JUnit test, identify the contract for the content being validated. A component may require a title, image reference, link target, and accessibility label. A content fragment may require fields with particular data types, while a page component may accept optional properties but still need a safe fallback when they are absent.
These rules should be expressed as observable behavior rather than implementation details. For example, a test should verify that a missing heading produces a defined result, not that a private helper method was called. This makes the suite resilient when the Sling Model is refactored or the repository implementation changes.
Validation can take place at multiple levels. Resource type and property checks protect repository structure, Sling Model tests verify adaptation and computed values, and service tests confirm that content is filtered or transformed correctly. Separating those responsibilities prevents one oversized test from becoming difficult to diagnose.
Create realistic AEM test fixtures
The AEM Mock Context from wcm.io is widely used for testing resources, requests, Sling Models, and OSGi services without starting a full AEM instance. A test can load JSON content, register model classes, set a current resource, and adapt that resource to the model under test. This provides a realistic repository shape with much less execution time than a deployment-based test.
Fixtures should resemble the paths and properties used in production, but they should remain deliberately small. A page with one component and two child items is usually more valuable than a large content dump because the expected behavior is easy to see. Store reusable JSON under test resources, and create special edge-case fixtures for missing properties, empty arrays, invalid references, and unexpected node types.
Use stable repository paths and explicit resource types in the fixture. Tests that rely on whichever resource happens to be current can pass for the wrong reason. Setting the resource directly also makes parameterized tests practical, allowing the same validation logic to run against valid, incomplete, and malformed content examples.
Select the right assertion boundary
AEM content tests are most effective when each test has one clear responsibility. A Sling Model test can verify adaptation, default values, child resource mapping, and exported properties. A validator service can be tested with a resource and a set of expected rules. A servlet test can focus on status codes and response data rather than re-testing the model underneath it.
Mockito remains useful for isolated Java classes, especially when an OSGi service has external dependencies. Use an AEM mock when repository behavior, resource adaptation, or Sling request state is part of the contract. Mixing both approaches is normal: mock the external service, but use an in-memory resource tree for the AEM behavior that the class actually owns.
| Validation target | Suitable test setup | Useful assertions |
|---|---|---|
| Sling Model | AEM Mock Context and registered model | Adaptation, properties, child items, defaults |
| OSGi service | JUnit with Mockito or lightweight context | Rule decisions, dependency calls, error handling |
| Content fragment | Mock repository fixture | Field values, required data, variation handling |
| Servlet or endpoint | Mock request and response | Status, headers, serialized payload |
| Repository structure | JSON fixture or integration test | Resource type, node names, property types |
| GraphQL response | Endpoint-level test or contract test | Schema shape, selected fields, null behavior |
Avoid asserting every getter in a model when several getters represent the same business rule. Prefer assertions that describe what a page or delivery consumer receives. For JSON output, compare meaningful fields and types rather than relying on a fragile full-string comparison unless ordering and formatting are part of the contract.
Validate fragments and structured delivery
Content fragments deserve focused tests because they often serve several channels. A fixture should cover the fragment path, model association, required fields, variation behavior, and references to assets or other content. When a model exposes a missing field, the test should document whether the result is null, empty, a default value, or a validation error.
The distinction between content fragments and experience fragments also affects test design. A useful content model comparison can clarify whether the test should focus on structured data for headless delivery or a reusable presentation component with layout and policy behavior. The test boundary should follow that purpose instead of treating every fragment as a simple property container.
If AEM content is delivered through GraphQL, validate the response contract separately from the repository fixture. Check that requested fields appear with the expected types, that absent optional values are handled consistently, and that nested references do not expose unintended repository details. The discussion of GraphQL content delivery is useful context when deciding which fields belong in an API-level test.
Cover invalid and changing content
A content validation suite earns its value from the cases that developers may overlook. Test empty strings, whitespace-only values, missing child resources, duplicate entries, invalid URLs, unsupported asset types, and references to unpublished or deleted content. These scenarios often appear after an author edits a dialog or imports data from another system.
Parameterized JUnit tests are well suited to these cases. Each input can identify the expected outcome, making the rule visible without duplicating setup code. For example, a link validator might accept an internal path, reject a malformed external URL, and return a warning for a missing label. The test name or parameter label should explain the business meaning of each case.
Content evolves over time, so tests should distinguish backward-compatible changes from breaking changes. Adding an optional property should not invalidate older fixtures. Renaming a required property or changing a field from a string to a multifield should trigger a deliberate update to both migration logic and tests. Keeping representative legacy fixtures helps reveal compatibility problems before they affect existing pages.
Keep the suite fast and maintainable
Run pure unit tests on every build and reserve heavier AEM integration tests for a separate verification stage when possible. Fast tests encourage developers to execute them frequently, while integration coverage can confirm annotations, OSGi wiring, repository registration, and exporter configuration that mocks cannot fully reproduce.
Use descriptive test names and arrange each test around setup, action, and assertion. A fixture builder can reduce repetition, but avoid hiding the properties that make a case important. When a test fails, a developer should be able to identify the content condition and the expected behavior without tracing several layers of utility code.
A practical validation workflow includes these priorities:
- Test required properties, defaults, and empty values for every public content model.
- Use AEM Mock Context for resource adaptation and repository-aware Sling Model behavior.
- Add parameterized cases for malformed paths, missing references, and invalid author input.
- Verify GraphQL or JSON output as a consumer contract, separate from internal model tests.
- Run integration tests for OSGi registration, exporter setup, and repository configuration.
Make validation part of delivery
JUnit tests should live beside the code and content contracts they protect. Include them in Maven builds, publish readable failure reports in continuous integration, and treat a failing validation test as a release issue rather than a test-maintenance task. When a requirement changes, update the fixture and assertion together so the new behavior remains explicit.
Start with the components and content models that carry the greatest publishing or integration risk. Add a small set of valid and invalid fixtures, verify the expected author-facing behavior, and then expand coverage around defects discovered in development. Build this habit into the AEM project now, so every content change receives a clear, repeatable check before it reaches production.