SlingContext
/AemContext
allow to create repository and populate it with test data
provided as JUnit4 @Rule
, so context instance is created before every test in class even if it’s unnecessary
the same ResourceResolverType
(eg. JCR_OAK
) is used for all tests, even if the faster one (eg. RESOURCERESOLVER_MOCK
) is sufficient for some tests
JUnit5
@Test
void shouldVerifySomething ( JcrOakAemContext context ) {
context . addModelsForPackage ( "co.nums.aem.blog.models" );
context . load (). json ( "co/nums/aem/blog/data/test.json" , "/content/blog" );
Resource testResource = context . resourceResolver (). getResource ( "/content/blog/test-node" );
TestModel sut = testResource . adaptTo ( TestModel . class );
// assertions
}
Extension
import io.wcm.testing.mock.aem.context.AemContextImpl ;
public class AemContext extends AemContextImpl {
protected void setUpContext () {
super . setUp ();
}
protected void tearDownContext () {
super . tearDown ();
}
}
import org.apache.sling.testing.mock.sling.ResourceResolverType ;
public class ResourceResolverMockAemContext extends AemContext {
ResourceResolverMockAemContext () {
setResourceResolverType ( ResourceResolverType . RESOURCERESOLVER_MOCK );
}
}
import org.apache.sling.testing.mock.sling.ResourceResolverType ;
public class JcrMockAemContext extends AemContext {
JcrMockAemContext () {
setResourceResolverType ( ResourceResolverType . JCR_MOCK );
}
}
import org.apache.sling.testing.mock.sling.ResourceResolverType ;
public class JcrOakAemContext extends AemContext {
JcrOakAemContext () {
setResourceResolverType ( ResourceResolverType . JCR_OAK );
}
}
import org.junit.jupiter.api.extension.AfterTestExecutionCallback ;
import org.junit.jupiter.api.extension.ExtensionContext ;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace ;
import org.junit.jupiter.api.extension.ExtensionContext.Store ;
import org.junit.jupiter.api.extension.ParameterContext ;
import org.junit.jupiter.api.extension.ParameterResolver ;
import java.lang.reflect.Type ;
import java.util.HashMap ;
import java.util.Map ;
import java.util.function.Supplier ;
import static java . util . Collections . unmodifiableMap ;
public class AemContextProvider implements ParameterResolver , AfterTestExecutionCallback {
private static final Namespace AEM_CONTEXT_NAMESPACE = Namespace . create ( AemContextProvider . class );
private static final Map < Type , Supplier <? extends AemContext >> CONTEXT_SUPPLIERS ;
static {
Map < Type , Supplier <? extends AemContext >> suppliers = new HashMap <>();
suppliers . put ( ResourceResolverMockAemContext . class , ResourceResolverMockAemContext: : new );
suppliers . put ( JcrMockAemContext . class , JcrMockAemContext: : new );
suppliers . put ( JcrOakAemContext . class , JcrOakAemContext: : new );
CONTEXT_SUPPLIERS = unmodifiableMap ( suppliers );
}
@Override
public boolean supportsParameter ( ParameterContext parameterContext , ExtensionContext extensionContext ) {
return CONTEXT_SUPPLIERS . containsKey ( parameterContext . getParameter (). getType ());
}
@Override
public Object resolveParameter ( ParameterContext parameterContext , ExtensionContext extensionContext ) {
AemContext aemContext = CONTEXT_SUPPLIERS . get ( parameterContext . getParameter (). getType ()). get ();
aemContext . setUpContext ();
getStore ( extensionContext ). put ( extensionContext . getRequiredTestMethod (), aemContext );
return aemContext ;
}
@Override
public void afterTestExecution ( ExtensionContext extensionContext ) {
AemContext aemContext = getAemContext ( extensionContext );
if ( aemContext != null ) {
aemContext . tearDownContext ();
}
}
private AemContext getAemContext ( ExtensionContext extensionContext ) {
return getStore ( extensionContext ). get ( extensionContext . getRequiredTestMethod (), AemContext . class );
}
private Store getStore ( ExtensionContext context ) {
return context . getStore ( AEM_CONTEXT_NAMESPACE );
}
}
Speed comparison
compared JUnit4 and JUnit5 test classes with:
2 empty test methods using JCR_OAK
context
20 empty test methods using RESOURCERESOLVER_MOCK
context
10 empty test methods without using context
JUnit4:
run from IntelliJ IDEA: 9.276s
run with mvn clean test
: 8.696s
JUnit5:
run from IntelliJ IDEA: 2.116s
run with mvn clean test
: 1.892s
only one class per JUnit4/JUnit5 was tested and ~1.5s is the time of creating AEM context for the first time, so once it is ready, next tests will be faster by about 15-20x (assuming that tests will use similar resource resolvers)
build speed improvement is significant
Source code
Notes
if project has JUnit3/JUnit4 and JUnit5 tests, then 2 engines (Jupiter and Vintage) are run during the build, so time earned on tests level can be stolen by second engine execution (few seconds of overhead) - it’s recommended to write only JUnit5 tests in new projects and migrate tests in existing ones
the same approach can be used for Sling Mocks (and other Sling-related mocks)