- Mockito — tasty mocking framework for unit tests in Java
- Sling Mocks — very lightweight, emulates all core features of Sling API and resource resolving. Covers 90% of what is required for typical Sling applications.
- Integration Tests (Sling Launchpad with Sling Testing Tools) — heavy-weight, for complex scenarios beyond scope of Sling Mocks
- Startup/Teardown of Sling environment for each test run
- Allow parallel execution of unit tests
- Support basic OSGi features (via OSGi mocks)
- Support Sling request, response and resource handling
- Support Sling Models
- Support simple loading and creating of content
- Allow combination with Mockito mocks
- Visualize code coverage in IDE and CI
- Maximum execution speed!
@Model(adaptables = SlingHttpServletRequest.class)
public class MatrixNavLink {
private MatrixNavigator root;
@Inject
public MatrixNavLink(@Self MatrixNavigator navigator) {
root = navigator.getMatrixNavigation();
}
public MatrixNavigator getRoot() {
return root;
}
}
@ExtendWith(MockitoExtension.class)
class MatrixNavLinkTest {
@Mock
private MatrixNavigator matrixNavigator;
private MatrixNavLink underTest;
@BeforeEach
void setUp() {
when(matrixNavigator.getMatrixNavigation())
.thenReturn(new NavigationPageItem("dummy"));
underTest = new MatrixNavLink(matrixNavigator);
}
@Test
void testGetRoot() {
assertNotNull(underTest.getRoot());
}
}
Pure Mockito becomes difficult when:
- Iterating over resource or page hierarchies
- Interaction with objects that have many methods for similar things (e.g. request params, Resource properties)
- Code that needs a complex content structure as test fixture
Result: 75% mocking code, 25% test code — risk of error in mocking is higher than in production code.
Sling Mocks is a mock implementation of the most important parts of Sling API and resource resolving. Builds on OSGi Mocks, JCR Mocks, and ResourceResolver Mocks.
A SlingContext object gives easy access to all Sling context objects:
- Registering OSGi services and Sling Models
- Loading content from JSON files
- Creating test content with simplified builder API
- Manipulating request state / investigating response state
Targets everything below the presentation layer — HTL scripts are not tested, but controllers and business logic are.
@ExtendWith(SlingContextExtension.class)
class MatrixTest {
private final SlingContext context = new SlingContext();
@Test
void testSomething() {
Resource resource = context.resourceResolver()
.getResource("/content/matrix/en");
// further testing
}
}
// Available context methods:
// context.bundleContext()
// context.resourceResolver()
// context.currentResource()
// context.request()
// context.requestPathInfo()
// context.response()
// context.slingScriptHelper()
// context.runMode("author")
- RESOURCERESOLVER_MOCK (default) — fastest, mocked resolver from Sling Testing. No JCR, eventing support, no search.
- RESOURCEPROVIDER_MOCK — same but using real Sling Resource Resolver impl. Multiple resource providers and FileVault XML loading.
- JCR_MOCK — mocked JCR with real Sling resource-JCR mapping. Still fast, but mocked search, no Observation/Versioning.
- JCR_OAK — real Oak with full Node Types, Search, Observation. No fulltext search (no Lucene indexes).
@Test
void testSomething() {
Resource resource = context.resourceResolver()
.getResource("/content/matrix/en");
ValueMap props = resource.getValueMap();
Iterator<Resource> children = resource.listChildren();
}
@Test
void testResourceOperations() throws Exception {
Resource resource = context.resourceResolver().create(
parentResource, "sentinel",
Map.of("prop1", "value1", "prop2", 123));
// further testing
context.resourceResolver().delete(resource);
}
// prepare sling request
context.request().setQueryString("param1=aaa¶m2=bbb");
context.requestPathInfo().setSelectorString("selector1.selector2");
context.requestPathInfo().setExtension("html");
context.requestPathInfo().setSuffix("/abc.html");
// set current resource
context.currentResource("/content/matrix/en");
// Request Mock supports: Parameters, RequestPathInfo,
// Attributes, Session, Headers, Cookies, Methods, Server Info
// Response Mock supports: Content Type, Headers, Cookies, Response Body
@BeforeEach
void setUp() {
// register models from package (includes all subpackages)
context.addModelsForPackage("org.matrix.models");
}
@Test
void testSomething() {
SentinelModel model = context.request()
.adaptTo(SentinelModel.class);
assertNotNull(model);
}
@BeforeEach
void setUp() {
// load JSON file into repository
context.load().json("/sample-data.json", "/content/matrix/en");
// load binary file into repository
context.load().binaryFile("/sample-file.gif",
"/content/binary/sample-file.gif");
}
@Test
void testSomething() {
Resource resource = context.resourceResolver()
.getResource("/content/matrix/en");
// further testing
}
// Tip: handcraft small JSON snippets per group of test cases.
// JSON download (.tidy.999.json) can be a good start,
// then remove everything not needed.
// create resource with properties
Resource resource = context.create().resource("/content/matrix/test1",
"prop1", "value1",
"prop2", 123);
// create hierarchies using ResourceBuilder
context.build().resource("/content/matrix/test1")
.siblingsMode()
.resource("child1", "prop1", "value1")
.resource("child2", "prop2", "value2")
.resource("child3", "prop3", "value3");
// Missing nodes in hierarchy are created automatically
// Properties are given as key,value,key,value... pairs or as Map
Normally you do not have to call resourceResolver.commit() in unit tests. In most cases tests work with one resource resolver and see all changes without commit.
context.load()automatically commits after importcontext.create()currently does not- Exception: If using JCR_OAK and JCR-specific features (Query, Observation), you must call
commit()first
// assert resource properties via hamcrest matcher
assertThat(resource, ResourceMatchers.props(
"prop1", "value1",
"prop2", 123));
// Checks that given properties are present with given values,
// although more properties may exist.
// Provided matchers:
// ResourceMatchers: name, properties, children, resource type
// ResourceCollectionMatchers: assert a set of resource paths
// ResourceIteratorMatchers: assert a set of resource paths
- There is no "best way" for all cases — use Mockito and/or Sling Mocks whatever suits best
- Goal: Tests that are easy to write and easy to maintain
- Tests should focus on the logic of the testable, and only this
- Goal: 80% unit test coverage for all projects
- Constantly check test coverage in your IDE
- Use Infinitest plugin (Eclipse/IntelliJ) to run tests automatically on each change