SlideShare a Scribd company logo
TDD with AEM
Jan Wloka
jan.wloka@quatico.com
Quatico Solutions AG
Test-driven Development with AEM
Test-driven Development with AEM
Text Image Component
Text Image Component
JSP Template
class
TextImageItem
class
TextImageController
<creates>
<creates>
<uses>
<uses>
Text Image Component
MVC
JSP Template
class
TextImageItem
class
TextImageController
<creates>
<creates>
<uses>
<uses>
Client
Server
Author
Deploy Create
Running the Component
JSP Template
class
TextImageItem
class
TextImageController
Client
Server
Author
Deploy Create
Running the Component
Round trip: Single feature ~1min
JSP Template
class
TextImageItem
class
TextImageController
Testing Text Image Component
AEM
Resources
App
TextImage
TextImage
Test
<creates>
<tests>
AEM Sling Testing API
@Before
public void setUp() throws Exception {
URL url = new URL(serverUrl);
httpClient = new HttpClient();
httpClient.getParams().setAuthenticationPreemptive(true);
httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials());
client = new SlingIntegrationTestClient(httpClient);
}
private void createPage(String path, Properties properties, Template template) throws Exception {
String[] pair = splitPathName(path);
HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand");
createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1])
.append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs());
if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) {
throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText());
}
if (!properties.isEmpty()) {
HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT);
changeReq.setQueryString(properties.toPairs(true));
if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) {
throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText());
}
}
}
private String createResource(String path, Properties properties) throws IOException {
String nodeUrl = serverUrl + path;
client.createNode(nodeUrl, properties.<String>toMap());
return nodeUrl;
}
AEM Sling Testing API
@Before
public void setUp() throws Exception {
URL url = new URL(serverUrl);
httpClient = new HttpClient();
httpClient.getParams().setAuthenticationPreemptive(true);
httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials());
client = new SlingIntegrationTestClient(httpClient);
}
private void createPage(String path, Properties properties, Template template) throws Exception {
String[] pair = splitPathName(path);
HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand");
createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1])
.append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs());
if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) {
throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText());
}
if (!properties.isEmpty()) {
HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT);
changeReq.setQueryString(properties.toPairs(true));
if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) {
throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText());
}
}
}
private String createResource(String path, Properties properties) throws IOException {
String nodeUrl = serverUrl + path;
client.createNode(nodeUrl, properties.<String>toMap());
return nodeUrl;
}
Setup Client
AEM Sling Testing API
@Before
public void setUp() throws Exception {
URL url = new URL(serverUrl);
httpClient = new HttpClient();
httpClient.getParams().setAuthenticationPreemptive(true);
httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials());
client = new SlingIntegrationTestClient(httpClient);
}
private void createPage(String path, Properties properties, Template template) throws Exception {
String[] pair = splitPathName(path);
HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand");
createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1])
.append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs());
if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) {
throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText());
}
if (!properties.isEmpty()) {
HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT);
changeReq.setQueryString(properties.toPairs(true));
if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) {
throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText());
}
}
}
private String createResource(String path, Properties properties) throws IOException {
String nodeUrl = serverUrl + path;
client.createNode(nodeUrl, properties.<String>toMap());
return nodeUrl;
}
Create
Page
AEM Sling Testing API
@Before
public void setUp() throws Exception {
URL url = new URL(serverUrl);
httpClient = new HttpClient();
httpClient.getParams().setAuthenticationPreemptive(true);
httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials());
client = new SlingIntegrationTestClient(httpClient);
}
private void createPage(String path, Properties properties, Template template) throws Exception {
String[] pair = splitPathName(path);
HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand");
createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1])
.append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs());
if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) {
throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText());
}
if (!properties.isEmpty()) {
HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT);
changeReq.setQueryString(properties.toPairs(true));
if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) {
throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText());
}
}
}
private String createResource(String path, Properties properties) throws IOException {
String nodeUrl = serverUrl + path;
client.createNode(nodeUrl, properties.<String>toMap());
return nodeUrl;
}
Setup Resource
Test: Image caption is shown
@Test
public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception {
ResourceResolver resolver = new ResourceResolverImpl(client);
createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate());
createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate());
createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate());
createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate());
Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content");
createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties());
Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage");
Properties imageProps = new Properties()
.append("source", "<sourceValue>")
.append("caption", "titleValue")
.append("sling:resourceType", ComponentType.IMAGE)
.append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT))
.append(JCR_LAST_MODIFIED_BY, "admin");
createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps);
TextImageController testObj = new TextImageController().setup(createContext(target, contentPage));
assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend());
}
Test: Image caption is shown
@Test
public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception {
ResourceResolver resolver = new ResourceResolverImpl(client);
createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate());
createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate());
createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate());
createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate());
Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content");
createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties());
Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage");
Properties imageProps = new Properties()
.append("source", "<sourceValue>")
.append("caption", "titleValue")
.append("sling:resourceType", ComponentType.IMAGE)
.append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT))
.append(JCR_LAST_MODIFIED_BY, "admin");
createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps);
TextImageController testObj = new TextImageController().setup(createContext(target, contentPage));
assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend());
}
Assemble
Test: Image caption is shown
@Test
public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception {
ResourceResolver resolver = new ResourceResolverImpl(client);
createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate());
createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate());
createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate());
createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate());
Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content");
createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties());
Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage");
Properties imageProps = new Properties()
.append("source", "<sourceValue>")
.append("caption", "titleValue")
.append("sling:resourceType", ComponentType.IMAGE)
.append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT))
.append(JCR_LAST_MODIFIED_BY, "admin");
createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps);
TextImageController testObj = new TextImageController().setup(createContext(target, contentPage));
assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend());
}
Assemble
Act
Test: Image caption is shown
@Test
public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception {
ResourceResolver resolver = new ResourceResolverImpl(client);
createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate());
createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate());
createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate());
createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate());
Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content");
createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties());
Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage");
Properties imageProps = new Properties()
.append("source", "<sourceValue>")
.append("caption", "titleValue")
.append("sling:resourceType", ComponentType.IMAGE)
.append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT))
.append(JCR_LAST_MODIFIED_BY, "admin");
createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps);
TextImageController testObj = new TextImageController().setup(createContext(target, contentPage));
assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend());
}
Assemble
Act
Assert
Integrated Tests with AEM
Author
Publish
boot2
JCR
JSP
JS/CSS
foo() : Foo
bar() : Bar
zip: Zip
zap : Zap
TextImage
testFooWithNull()
testFooWithOk()
testBarWithEmpty()
testBarWithResult()
TextImageTest
Request
Response
Client
Server
Integrated Tests with AEM
Round trip: ~80 tested features ~2m30s
Author
Publish
boot2
JCR
JSP
JS/CSS
foo() : Foo
bar() : Bar
zip: Zip
zap : Zap
TextImage
testFooWithNull()
testFooWithOk()
testBarWithEmpty()
testBarWithResult()
TextImageTest
Request
Response
Client
Server
It works, but still hurts!
Client Server
AEM
App
Test
Test
Test
Test
Test
It works, but still hurts!
Client Server
AEM
App
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
Test
It works, but still hurts!
Client Server
AEM
App
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
Test
Delay (or prevent) feedback
Hard to write
Difficult to maintain
Flaky results
Low coverage
A unified testing API
AEM
App
App
Test API
A unified testing API
Test
API
AEM
Resources
App
TextImage
TextImage
Test <uses>
<tests>
API for creating Pages
class Pages {
aPresencePage(String, Object…)
aLanguagePage(String, Object…)
aHomePage(String, Object…)
aNewsPage(String, Object…)
aRssFeedPage(String, Object…)
aDetailPage(String, Object…)
aContactPage(String, Object…)
aTagPage(String, Object…)
aPageContext(Resource, Object…)
...
API for creating Components
class Components {
aParSys(String, Object…)
aLink(String, Object…)
aLinkList(String, Object…)
aServiceNavigation(String, Object…)
anImage(String, Object…)
aSlideshow(String, Object…)
aFooter(String, Object…)
aTeaser(String, Object…)
aContactBox(String, Object…)
aRssFeed(String, Object…)
aSearchFilter(String, Object…)
aPager(String, Object…)
aForm(String, Object…)
...
Builder API for Everything
Pages
Components
Resources
Assets
AemClient
TestSetup
TextImage
ControllerTest
<uses>
<creates>
<creates>
<creates><creates>
<creates>
TextImage Test with Builders
public class TextImageControllerTest extends IntegrationTestBase {
@Test

public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception {


Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);

Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");

$.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");



TextImageController testObj = new TextImageController().setup(
$.aPageContextWithComponent(target, contentPage));
assertEquals("expected", testObj.getImageLegend());
}
TextImage Test with Builders
public class TextImageControllerTest extends IntegrationTestBase {
@Test

public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception {


Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);

Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");

$.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");



TextImageController testObj = new TextImageController().setup(
$.aPageContextWithComponent(target, contentPage));
assertEquals("expected", testObj.getImageLegend());
}
Assemble
TextImage Test with Builders
public class TextImageControllerTest extends IntegrationTestBase {
@Test

public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception {


Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);

Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");

$.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");



TextImageController testObj = new TextImageController().setup(
$.aPageContextWithComponent(target, contentPage));
assertEquals("expected", testObj.getImageLegend());
}
Assemble
Act
TextImage Test with Builders
public class TextImageControllerTest extends IntegrationTestBase {
@Test

public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception {


Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);

Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");

$.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");



TextImageController testObj = new TextImageController().setup(
$.aPageContextWithComponent(target, contentPage));
assertEquals("expected", testObj.getImageLegend());
}
Assemble
Assert
Act
“Fluent Builder” makes use simple
• Fluent Builder API
• Conciseness: Fluent syntax, smart defaults, var args
• Uniformity: Consistent semantics
• Pervasiveness: Availability everywhere
Map<String, String> httpMapping = new Properties(
DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(),
DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap();
$.service(
new FileDeliveryService(),
$.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping)));
<R> R service(R service, Object... requiredServices) throws Exception;
<R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
“Fluent Builder” makes use simple
• Fluent Builder API
• Conciseness: Fluent syntax, smart defaults, var args
• Uniformity: Consistent semantics
• Pervasiveness: Availability everywhere
Map<String, String> httpMapping = new Properties(
DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(),
DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap();
$.service(
new FileDeliveryService(),
$.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping)));
<R> R service(R service, Object... requiredServices) throws Exception;
<R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
“Fluent Builder” makes use simple
• Fluent Builder API
• Conciseness: Fluent syntax, smart defaults, var args
• Uniformity: Consistent semantics
• Pervasiveness: Availability everywhere
Map<String, String> httpMapping = new Properties(
DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(),
DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap();
$.service(
new FileDeliveryService(),
$.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping)));
<R> R service(R service, Object... requiredServices) throws Exception;
<R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
“Fluent Builder” makes use simple
• Fluent Builder API
• Conciseness: Fluent syntax, smart defaults, var args
• Uniformity: Consistent semantics
• Pervasiveness: Availability everywhere
Map<String, String> httpMapping = new Properties(
DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(),
DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap();
$.service(
new FileDeliveryService(),
$.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping)));
<R> R service(R service, Object... requiredServices) throws Exception;
<R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
“Dynamic Proxy” makes it available
Assets
<creates>
<creates>
Pages
Components
Resources
AemClient
Integration
TestBase
TextImage
ControllerTest
<creates><creates>
<creates>
“Dynamic Proxy” makes it available
ITestSetup
IPagesIComponents IResourcesIAssetsIAemClient
Assets
<creates>
<creates>
Pages
Components
Resources
AemClient
Integration
TestBase
TextImage
ControllerTest
<creates><creates>
<creates>
“Dynamic Proxy” makes it available
ITestSetup
IPagesIComponents IResourcesIAssetsIAemClient
Assets
<creates>
<creates>
Pages
Components
Resources
AemClient
Integration
TestBase
TextImage
ControllerTest
<creates><creates>
<creates>
<owns>
“Dynamic Proxy” makes it available
ITestSetup
IPagesIComponents IResourcesIAssetsIAemClient
<creates>
<creates> MockPages
MockComponents
MockResources
MockAssets
MockClient
UnitTest
TestBase
<owns>
TextImage
ControllerTest
<creates>
<creates><creates>
Initialize Integration Test Context
public abstract class IntegrationTestBase {



public static final String MANDANT_TESTS = “/content/foobar”;

public static final Locale TEST_LOCALE = new Locale("ko");

private final Set<String> resourcesToDelete = new HashSet<>();
public ITestSetup $;

protected SlingClient slingClient;

protected ResourceResolver resolver;



@Before

public void setUpTestContext() throws Exception {

slingClient = new SlingClient(SERVER_URL);

resolver = new ResourceResolverImpl(slingClient);

IResources resources = new Resources(slingClient, resolver);
IPages pages = new Pages(slingClient, resolver);

IComponents components = new Components(resources);

$ = SetupFactory.create(ITestSetup.class).getSetup(resources, pages, components,
new Assets(resources), new AemClient(resolver), new Services(components));
slingClient.deleteResource(MANDANT_TESTS);
}
Initialize Integration Test Context
public abstract class IntegrationTestBase {



public static final String MANDANT_TESTS = “/content/foobar”;

public static final Locale TEST_LOCALE = new Locale("ko");

private final Set<String> resourcesToDelete = new HashSet<>();
public ITestSetup $;

protected SlingClient slingClient;

protected ResourceResolver resolver;



@Before

public void setUpTestContext() throws Exception {

slingClient = new SlingClient(SERVER_URL);

resolver = new ResourceResolverImpl(slingClient);

IResources resources = new Resources(slingClient, resolver);
IPages pages = new Pages(slingClient, resolver);

IComponents components = new Components(resources);

$ = SetupFactory.create(ITestSetup.class).getSetup(resources, pages, components,
new Assets(resources), new AemClient(resolver), new Services(components));
slingClient.deleteResource(MANDANT_TESTS);
}
Initialize Unit Test Context
public abstract class UnitTestBase {



public static final String MANDANT_TESTS = “/content/foobar”;

public static final Locale TEST_LOCALE = new Locale("ko");

public ITestSetup $;



@Before

public void setUpTestContext() throws Exception {

Locale.setDefault(TEST_LOCALE);

IAemClient client = new MockAemClient();

IResources resources = new MockResources(client);

IPages pages = new MockPages(resources, client);

IAssets assets = new MockAssets(resources, client);

IRequest request = new MockRequest(resources, client);

IComponents components = new MockComponents(resources);

$ = SetupFactory.create(ITestSetup.class).getSetup(client, resources, assets,
request, pages, components, new MockJspTags(resources, pages),
new MockServices());

}
Initialize Unit Test Context
public abstract class UnitTestBase {



public static final String MANDANT_TESTS = “/content/foobar”;

public static final Locale TEST_LOCALE = new Locale("ko");

public ITestSetup $;



@Before

public void setUpTestContext() throws Exception {

Locale.setDefault(TEST_LOCALE);

IAemClient client = new MockAemClient();

IResources resources = new MockResources(client);

IPages pages = new MockPages(resources, client);

IAssets assets = new MockAssets(resources, client);

IRequest request = new MockRequest(resources, client);

IComponents components = new MockComponents(resources);

$ = SetupFactory.create(ITestSetup.class).getSetup(client, resources, assets,
request, pages, components, new MockJspTags(resources, pages),
new MockServices());

}
Development Cycle with AEM
New
Integrated
Test
Copy Test
to
UnitTests
Adapt
Mocks
Test-drive
New Feature
Write Acceptance Test first
@Test
public void testIsShowTitleBelowWithImageAndSizeNormalAndPositionLeftReturnsTrue() {
Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content");
Resource asset = $.anAsset("/content/dam/quatico-test/test-image.jpg");
Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage",
"text", "<b>hello</b>");
$.anImage(contentPage.getPath() + "/jcr:content/textimage/image",
FILE_REFERENCE, asset.getPath(),
"imageSize", NORMAL,
"imagePosition", LEFT_ABOVE);
TextImageController testObj = new TextImageController().setup(
$.aPageContextWithComponent(target, contentPage));
assertTrue(testObj.isShowTitleBelow());
}
TDD away, with Unit Tests
@Test
public void testIsShowTitleBelowWithImagePresentAndSizeNormalAndPositionLeftReturnsTrue() throws Exception {
Resource asset = $.anAsset("/content/dam/foo.jpg");
Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>");
$.anImage(“/a/b/c/jcr:content/textimage/image",
FILE_REFERENCE, asset.getPath(), "imageSize", NORMAL, "imagePosition", LEFT_ABOVE);
assertTrue(aTextImageController(target).isShowTitleBelow());
}
@Test
public void testIsShowTitleBelowWithImagePresentAndSizeSmallAndPositionLeftReturnFalse() throws Exception {
Resource asset = $.anAsset("/content/dam/foo.jpg");
Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>");
$.anImage("/a/b/c/jcr:content/textimage/image",
FILE_REFERENCE, asset.getPath(), "imageSize", SMALL, "imagePosition", LEFT_ABOVE);
assertFalse(aTextImageController(target).isShowTitleBelow());
}
@Test
public void testIsShowTitleBelowWithNoImagePresentReturnsFalse() throws Exception {
Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>");
assertFalse(aTextImageController(target).isShowTitleBelow());
}
@Test
public void testValidateWithValidTextResourceYieldsOk() throws Exception {
Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>");
assertStatusEquals(Ok(), aTextImageController(target).validate());
}
@Test
public void testValidateWithEmptyTextResourceYieldsWarning() throws Exception {
Resource target = aTextImage("/a/b/c");
Status actual = aTextImageController(target).validate();
assertTrue(actual.isWarning());
}
Unit Testing with AEM
Mock AEM
JCR
foo() : Foo
bar() : Bar
zip: Zip
zap : Zap
TextImage
testFooWithNull()
testFooWithOk()
testBarWithEmpty()
testBarWithResult()
TextImageTest
Client
Unit Testing with AEM
Round trip: ~5000 unit tests ~30s
Mock AEM
JCR
foo() : Foo
bar() : Bar
zip: Zip
zap : Zap
TextImage
testFooWithNull()
testFooWithOk()
testBarWithEmpty()
testBarWithResult()
TextImageTest
Client
Conclusion: No excuses
• Best practices for initializing, stubbing and
mocking objects are made consistently available
• Test setups cost virtually nothing
• You write more simple, reliable, maintainable tests
• Listen to their feedback more frequently
• New developers quickly adapt to best practices
Yes, it’s possible
UT
Performance Testing
Integration
Testing
Human Testing
End-To-End Testing
HT
Integration Testing
Performance
Testing
Unit Testing
End-To-End Testing
Thank You.
jan.wloka@quatico.com
@crashtester

More Related Content

What's hot (20)

PDF
Nativescript angular
Christoffer Noring
 
PPTX
Open Source Ajax Solution @OSDC.tw 2009
Robbie Cheng
 
PDF
Crossing platforms with JavaScript & React
Robert DeLuca
 
PDF
Node.js in action
Simon Su
 
PDF
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
PPTX
Test and profile your Windows Phone 8 App
Michele Capra
 
PPTX
How to perform debounce in react
BOSC Tech Labs
 
PPT
Developing application for Windows Phone 7 in TDD
Michele Capra
 
RTF
Easy Button
Adam Dale
 
PDF
Redux for ReactJS Programmers
David Rodenas
 
PDF
Graphql, REST and Apollo
Christoffer Noring
 
PDF
The Ring programming language version 1.7 book - Part 47 of 196
Mahmoud Samir Fayed
 
PDF
Test-Driven Development of AngularJS Applications
FITC
 
PDF
Persisting Data on SQLite using Room
Nelson Glauber Leal
 
PDF
Basic Tutorial of React for Programmers
David Rodenas
 
PPTX
Basics of AngularJS
Filip Janevski
 
PDF
Overview Of Lift Framework
Xebia IT Architects
 
PDF
Overview of The Scala Based Lift Web Framework
IndicThreads
 
PDF
L2 Web App Development Guest Lecture At University of Surrey 20/11/09
Daniel Bryant
 
PDF
Sane Async Patterns
TrevorBurnham
 
Nativescript angular
Christoffer Noring
 
Open Source Ajax Solution @OSDC.tw 2009
Robbie Cheng
 
Crossing platforms with JavaScript & React
Robert DeLuca
 
Node.js in action
Simon Su
 
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
Test and profile your Windows Phone 8 App
Michele Capra
 
How to perform debounce in react
BOSC Tech Labs
 
Developing application for Windows Phone 7 in TDD
Michele Capra
 
Easy Button
Adam Dale
 
Redux for ReactJS Programmers
David Rodenas
 
Graphql, REST and Apollo
Christoffer Noring
 
The Ring programming language version 1.7 book - Part 47 of 196
Mahmoud Samir Fayed
 
Test-Driven Development of AngularJS Applications
FITC
 
Persisting Data on SQLite using Room
Nelson Glauber Leal
 
Basic Tutorial of React for Programmers
David Rodenas
 
Basics of AngularJS
Filip Janevski
 
Overview Of Lift Framework
Xebia IT Architects
 
Overview of The Scala Based Lift Web Framework
IndicThreads
 
L2 Web App Development Guest Lecture At University of Surrey 20/11/09
Daniel Bryant
 
Sane Async Patterns
TrevorBurnham
 

Similar to Test-driven Development with AEM (9)

PDF
Building domain-specific testing tools : lessons learned from the Apache Slin...
Robert Munteanu
 
PPTX
Integration patterns in AEM 6
Yuval Ararat
 
PPTX
EPAM IT WEEK: AEM & TDD. It's so boring...
Andrew Manuev
 
PPT
Build Your Own CMS with Apache Sling
Bob Paulin
 
PDF
Spring 3 - Der dritte Frühling
Thorsten Kamann
 
PDF
CIRCUIT 2015 - Content API's For AEM Sites
ICF CIRCUIT
 
PDF
Spring Framework 4.1
Sam Brannen
 
ODP
Rapid JCR Applications Development with Sling
Felix Meschberger
 
PDF
five Sling features you should know
connectwebex
 
Building domain-specific testing tools : lessons learned from the Apache Slin...
Robert Munteanu
 
Integration patterns in AEM 6
Yuval Ararat
 
EPAM IT WEEK: AEM & TDD. It's so boring...
Andrew Manuev
 
Build Your Own CMS with Apache Sling
Bob Paulin
 
Spring 3 - Der dritte Frühling
Thorsten Kamann
 
CIRCUIT 2015 - Content API's For AEM Sites
ICF CIRCUIT
 
Spring Framework 4.1
Sam Brannen
 
Rapid JCR Applications Development with Sling
Felix Meschberger
 
five Sling features you should know
connectwebex
 
Ad

Recently uploaded (20)

PPTX
IObit Driver Booster Pro Crack Download Latest Version
chaudhryakashoo065
 
PDF
What Is an Internal Quality Audit and Why It Matters for Your QMS
BizPortals365
 
PDF
How DeepSeek Beats ChatGPT: Cost Comparison and Key Differences
sumitpurohit810
 
PDF
Automated Test Case Repair Using Language Models
Lionel Briand
 
PPTX
For my supp to finally picking supp that work
necas19388
 
PPTX
IDM Crack with Internet Download Manager 6.42 [Latest 2025]
HyperPc soft
 
PPTX
EO4EU Ocean Monitoring: Maritime Weather Routing Optimsation Use Case
EO4EU
 
PDF
Alur Perkembangan Software dan Jaringan Komputer
ssuser754303
 
PPTX
B2C EXTRANET | EXTRANET WEBSITE | EXTRANET INTEGRATION
philipnathen82
 
PDF
capitulando la keynote de GrafanaCON 2025 - Madrid
Imma Valls Bernaus
 
PDF
Writing Maintainable Playwright Tests with Ease
Shubham Joshi
 
PDF
Code Once; Run Everywhere - A Beginner’s Journey with React Native
Hasitha Walpola
 
PPTX
Wondershare Filmora Crack 14.5.18 + Key Full Download [Latest 2025]
HyperPc soft
 
PDF
Designing Accessible Content Blocks (1).pdf
jaclynmennie1
 
PPTX
Avast Premium Security crack 25.5.6162 + License Key 2025
HyperPc soft
 
PDF
>Nitro Pro Crack 14.36.1.0 + Keygen Free Download [Latest]
utfefguu
 
PPTX
Iobit Driver Booster Pro 12 Crack Free Download
chaudhryakashoo065
 
PPTX
Seamless-Image-Conversion-From-Raster-to-wrt-rtx-rtx.pptx
Quick Conversion Services
 
PDF
Cloud computing Lec 02 - virtualization.pdf
asokawennawatte
 
PDF
Laboratory Workflows Digitalized and live in 90 days with Scifeon´s SAPPA P...
info969686
 
IObit Driver Booster Pro Crack Download Latest Version
chaudhryakashoo065
 
What Is an Internal Quality Audit and Why It Matters for Your QMS
BizPortals365
 
How DeepSeek Beats ChatGPT: Cost Comparison and Key Differences
sumitpurohit810
 
Automated Test Case Repair Using Language Models
Lionel Briand
 
For my supp to finally picking supp that work
necas19388
 
IDM Crack with Internet Download Manager 6.42 [Latest 2025]
HyperPc soft
 
EO4EU Ocean Monitoring: Maritime Weather Routing Optimsation Use Case
EO4EU
 
Alur Perkembangan Software dan Jaringan Komputer
ssuser754303
 
B2C EXTRANET | EXTRANET WEBSITE | EXTRANET INTEGRATION
philipnathen82
 
capitulando la keynote de GrafanaCON 2025 - Madrid
Imma Valls Bernaus
 
Writing Maintainable Playwright Tests with Ease
Shubham Joshi
 
Code Once; Run Everywhere - A Beginner’s Journey with React Native
Hasitha Walpola
 
Wondershare Filmora Crack 14.5.18 + Key Full Download [Latest 2025]
HyperPc soft
 
Designing Accessible Content Blocks (1).pdf
jaclynmennie1
 
Avast Premium Security crack 25.5.6162 + License Key 2025
HyperPc soft
 
>Nitro Pro Crack 14.36.1.0 + Keygen Free Download [Latest]
utfefguu
 
Iobit Driver Booster Pro 12 Crack Free Download
chaudhryakashoo065
 
Seamless-Image-Conversion-From-Raster-to-wrt-rtx-rtx.pptx
Quick Conversion Services
 
Cloud computing Lec 02 - virtualization.pdf
asokawennawatte
 
Laboratory Workflows Digitalized and live in 90 days with Scifeon´s SAPPA P...
info969686
 
Ad

Test-driven Development with AEM

  • 1. TDD with AEM Jan Wloka [email protected] Quatico Solutions AG
  • 5. Text Image Component JSP Template class TextImageItem class TextImageController <creates> <creates> <uses> <uses>
  • 6. Text Image Component MVC JSP Template class TextImageItem class TextImageController <creates> <creates> <uses> <uses>
  • 7. Client Server Author Deploy Create Running the Component JSP Template class TextImageItem class TextImageController
  • 8. Client Server Author Deploy Create Running the Component Round trip: Single feature ~1min JSP Template class TextImageItem class TextImageController
  • 9. Testing Text Image Component AEM Resources App TextImage TextImage Test <creates> <tests>
  • 10. AEM Sling Testing API @Before public void setUp() throws Exception { URL url = new URL(serverUrl); httpClient = new HttpClient(); httpClient.getParams().setAuthenticationPreemptive(true); httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials()); client = new SlingIntegrationTestClient(httpClient); } private void createPage(String path, Properties properties, Template template) throws Exception { String[] pair = splitPathName(path); HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand"); createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1]) .append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs()); if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) { throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText()); } if (!properties.isEmpty()) { HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT); changeReq.setQueryString(properties.toPairs(true)); if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) { throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText()); } } } private String createResource(String path, Properties properties) throws IOException { String nodeUrl = serverUrl + path; client.createNode(nodeUrl, properties.<String>toMap()); return nodeUrl; }
  • 11. AEM Sling Testing API @Before public void setUp() throws Exception { URL url = new URL(serverUrl); httpClient = new HttpClient(); httpClient.getParams().setAuthenticationPreemptive(true); httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials()); client = new SlingIntegrationTestClient(httpClient); } private void createPage(String path, Properties properties, Template template) throws Exception { String[] pair = splitPathName(path); HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand"); createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1]) .append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs()); if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) { throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText()); } if (!properties.isEmpty()) { HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT); changeReq.setQueryString(properties.toPairs(true)); if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) { throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText()); } } } private String createResource(String path, Properties properties) throws IOException { String nodeUrl = serverUrl + path; client.createNode(nodeUrl, properties.<String>toMap()); return nodeUrl; } Setup Client
  • 12. AEM Sling Testing API @Before public void setUp() throws Exception { URL url = new URL(serverUrl); httpClient = new HttpClient(); httpClient.getParams().setAuthenticationPreemptive(true); httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials()); client = new SlingIntegrationTestClient(httpClient); } private void createPage(String path, Properties properties, Template template) throws Exception { String[] pair = splitPathName(path); HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand"); createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1]) .append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs()); if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) { throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText()); } if (!properties.isEmpty()) { HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT); changeReq.setQueryString(properties.toPairs(true)); if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) { throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText()); } } } private String createResource(String path, Properties properties) throws IOException { String nodeUrl = serverUrl + path; client.createNode(nodeUrl, properties.<String>toMap()); return nodeUrl; } Create Page
  • 13. AEM Sling Testing API @Before public void setUp() throws Exception { URL url = new URL(serverUrl); httpClient = new HttpClient(); httpClient.getParams().setAuthenticationPreemptive(true); httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials()); client = new SlingIntegrationTestClient(httpClient); } private void createPage(String path, Properties properties, Template template) throws Exception { String[] pair = splitPathName(path); HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand"); createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1]) .append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs()); if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) { throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText()); } if (!properties.isEmpty()) { HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT); changeReq.setQueryString(properties.toPairs(true)); if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) { throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText()); } } } private String createResource(String path, Properties properties) throws IOException { String nodeUrl = serverUrl + path; client.createNode(nodeUrl, properties.<String>toMap()); return nodeUrl; } Setup Resource
  • 14. Test: Image caption is shown @Test public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception { ResourceResolver resolver = new ResourceResolverImpl(client); createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate()); createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate()); createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate()); createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate()); Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content"); createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties()); Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage"); Properties imageProps = new Properties() .append("source", "<sourceValue>") .append("caption", "titleValue") .append("sling:resourceType", ComponentType.IMAGE) .append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT)) .append(JCR_LAST_MODIFIED_BY, "admin"); createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps); TextImageController testObj = new TextImageController().setup(createContext(target, contentPage)); assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend()); }
  • 15. Test: Image caption is shown @Test public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception { ResourceResolver resolver = new ResourceResolverImpl(client); createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate()); createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate()); createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate()); createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate()); Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content"); createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties()); Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage"); Properties imageProps = new Properties() .append("source", "<sourceValue>") .append("caption", "titleValue") .append("sling:resourceType", ComponentType.IMAGE) .append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT)) .append(JCR_LAST_MODIFIED_BY, "admin"); createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps); TextImageController testObj = new TextImageController().setup(createContext(target, contentPage)); assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend()); } Assemble
  • 16. Test: Image caption is shown @Test public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception { ResourceResolver resolver = new ResourceResolverImpl(client); createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate()); createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate()); createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate()); createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate()); Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content"); createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties()); Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage"); Properties imageProps = new Properties() .append("source", "<sourceValue>") .append("caption", "titleValue") .append("sling:resourceType", ComponentType.IMAGE) .append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT)) .append(JCR_LAST_MODIFIED_BY, "admin"); createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps); TextImageController testObj = new TextImageController().setup(createContext(target, contentPage)); assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend()); } Assemble Act
  • 17. Test: Image caption is shown @Test public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception { ResourceResolver resolver = new ResourceResolverImpl(client); createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate()); createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate()); createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate()); createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate()); Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content"); createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties()); Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage"); Properties imageProps = new Properties() .append("source", "<sourceValue>") .append("caption", "titleValue") .append("sling:resourceType", ComponentType.IMAGE) .append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT)) .append(JCR_LAST_MODIFIED_BY, "admin"); createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps); TextImageController testObj = new TextImageController().setup(createContext(target, contentPage)); assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend()); } Assemble Act Assert
  • 18. Integrated Tests with AEM Author Publish boot2 JCR JSP JS/CSS foo() : Foo bar() : Bar zip: Zip zap : Zap TextImage testFooWithNull() testFooWithOk() testBarWithEmpty() testBarWithResult() TextImageTest Request Response Client Server
  • 19. Integrated Tests with AEM Round trip: ~80 tested features ~2m30s Author Publish boot2 JCR JSP JS/CSS foo() : Foo bar() : Bar zip: Zip zap : Zap TextImage testFooWithNull() testFooWithOk() testBarWithEmpty() testBarWithResult() TextImageTest Request Response Client Server
  • 20. It works, but still hurts! Client Server AEM App Test Test Test Test Test
  • 21. It works, but still hurts! Client Server AEM App Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test Test
  • 22. It works, but still hurts! Client Server AEM App Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test Test Delay (or prevent) feedback Hard to write Difficult to maintain Flaky results Low coverage
  • 23. A unified testing API AEM App App Test API
  • 24. A unified testing API Test API AEM Resources App TextImage TextImage Test <uses> <tests>
  • 25. API for creating Pages class Pages { aPresencePage(String, Object…) aLanguagePage(String, Object…) aHomePage(String, Object…) aNewsPage(String, Object…) aRssFeedPage(String, Object…) aDetailPage(String, Object…) aContactPage(String, Object…) aTagPage(String, Object…) aPageContext(Resource, Object…) ...
  • 26. API for creating Components class Components { aParSys(String, Object…) aLink(String, Object…) aLinkList(String, Object…) aServiceNavigation(String, Object…) anImage(String, Object…) aSlideshow(String, Object…) aFooter(String, Object…) aTeaser(String, Object…) aContactBox(String, Object…) aRssFeed(String, Object…) aSearchFilter(String, Object…) aPager(String, Object…) aForm(String, Object…) ...
  • 27. Builder API for Everything Pages Components Resources Assets AemClient TestSetup TextImage ControllerTest <uses> <creates> <creates> <creates><creates> <creates>
  • 28. TextImage Test with Builders public class TextImageControllerTest extends IntegrationTestBase { @Test
 public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception { 
 Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);
 Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");
 $.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");
 
 TextImageController testObj = new TextImageController().setup( $.aPageContextWithComponent(target, contentPage)); assertEquals("expected", testObj.getImageLegend()); }
  • 29. TextImage Test with Builders public class TextImageControllerTest extends IntegrationTestBase { @Test
 public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception { 
 Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);
 Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");
 $.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");
 
 TextImageController testObj = new TextImageController().setup( $.aPageContextWithComponent(target, contentPage)); assertEquals("expected", testObj.getImageLegend()); } Assemble
  • 30. TextImage Test with Builders public class TextImageControllerTest extends IntegrationTestBase { @Test
 public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception { 
 Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);
 Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");
 $.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");
 
 TextImageController testObj = new TextImageController().setup( $.aPageContextWithComponent(target, contentPage)); assertEquals("expected", testObj.getImageLegend()); } Assemble Act
  • 31. TextImage Test with Builders public class TextImageControllerTest extends IntegrationTestBase { @Test
 public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception { 
 Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);
 Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");
 $.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");
 
 TextImageController testObj = new TextImageController().setup( $.aPageContextWithComponent(target, contentPage)); assertEquals("expected", testObj.getImageLegend()); } Assemble Assert Act
  • 32. “Fluent Builder” makes use simple • Fluent Builder API • Conciseness: Fluent syntax, smart defaults, var args • Uniformity: Consistent semantics • Pervasiveness: Availability everywhere Map<String, String> httpMapping = new Properties( DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(), DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap(); $.service( new FileDeliveryService(), $.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping))); <R> R service(R service, Object... requiredServices) throws Exception; <R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
  • 33. “Fluent Builder” makes use simple • Fluent Builder API • Conciseness: Fluent syntax, smart defaults, var args • Uniformity: Consistent semantics • Pervasiveness: Availability everywhere Map<String, String> httpMapping = new Properties( DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(), DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap(); $.service( new FileDeliveryService(), $.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping))); <R> R service(R service, Object... requiredServices) throws Exception; <R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
  • 34. “Fluent Builder” makes use simple • Fluent Builder API • Conciseness: Fluent syntax, smart defaults, var args • Uniformity: Consistent semantics • Pervasiveness: Availability everywhere Map<String, String> httpMapping = new Properties( DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(), DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap(); $.service( new FileDeliveryService(), $.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping))); <R> R service(R service, Object... requiredServices) throws Exception; <R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
  • 35. “Fluent Builder” makes use simple • Fluent Builder API • Conciseness: Fluent syntax, smart defaults, var args • Uniformity: Consistent semantics • Pervasiveness: Availability everywhere Map<String, String> httpMapping = new Properties( DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(), DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap(); $.service( new FileDeliveryService(), $.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping))); <R> R service(R service, Object... requiredServices) throws Exception; <R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
  • 36. “Dynamic Proxy” makes it available Assets <creates> <creates> Pages Components Resources AemClient Integration TestBase TextImage ControllerTest <creates><creates> <creates>
  • 37. “Dynamic Proxy” makes it available ITestSetup IPagesIComponents IResourcesIAssetsIAemClient Assets <creates> <creates> Pages Components Resources AemClient Integration TestBase TextImage ControllerTest <creates><creates> <creates>
  • 38. “Dynamic Proxy” makes it available ITestSetup IPagesIComponents IResourcesIAssetsIAemClient Assets <creates> <creates> Pages Components Resources AemClient Integration TestBase TextImage ControllerTest <creates><creates> <creates> <owns>
  • 39. “Dynamic Proxy” makes it available ITestSetup IPagesIComponents IResourcesIAssetsIAemClient <creates> <creates> MockPages MockComponents MockResources MockAssets MockClient UnitTest TestBase <owns> TextImage ControllerTest <creates> <creates><creates>
  • 40. Initialize Integration Test Context public abstract class IntegrationTestBase {
 
 public static final String MANDANT_TESTS = “/content/foobar”;
 public static final Locale TEST_LOCALE = new Locale("ko");
 private final Set<String> resourcesToDelete = new HashSet<>(); public ITestSetup $;
 protected SlingClient slingClient;
 protected ResourceResolver resolver;
 
 @Before
 public void setUpTestContext() throws Exception {
 slingClient = new SlingClient(SERVER_URL);
 resolver = new ResourceResolverImpl(slingClient);
 IResources resources = new Resources(slingClient, resolver); IPages pages = new Pages(slingClient, resolver);
 IComponents components = new Components(resources);
 $ = SetupFactory.create(ITestSetup.class).getSetup(resources, pages, components, new Assets(resources), new AemClient(resolver), new Services(components)); slingClient.deleteResource(MANDANT_TESTS); }
  • 41. Initialize Integration Test Context public abstract class IntegrationTestBase {
 
 public static final String MANDANT_TESTS = “/content/foobar”;
 public static final Locale TEST_LOCALE = new Locale("ko");
 private final Set<String> resourcesToDelete = new HashSet<>(); public ITestSetup $;
 protected SlingClient slingClient;
 protected ResourceResolver resolver;
 
 @Before
 public void setUpTestContext() throws Exception {
 slingClient = new SlingClient(SERVER_URL);
 resolver = new ResourceResolverImpl(slingClient);
 IResources resources = new Resources(slingClient, resolver); IPages pages = new Pages(slingClient, resolver);
 IComponents components = new Components(resources);
 $ = SetupFactory.create(ITestSetup.class).getSetup(resources, pages, components, new Assets(resources), new AemClient(resolver), new Services(components)); slingClient.deleteResource(MANDANT_TESTS); }
  • 42. Initialize Unit Test Context public abstract class UnitTestBase {
 
 public static final String MANDANT_TESTS = “/content/foobar”;
 public static final Locale TEST_LOCALE = new Locale("ko");
 public ITestSetup $;
 
 @Before
 public void setUpTestContext() throws Exception {
 Locale.setDefault(TEST_LOCALE);
 IAemClient client = new MockAemClient();
 IResources resources = new MockResources(client);
 IPages pages = new MockPages(resources, client);
 IAssets assets = new MockAssets(resources, client);
 IRequest request = new MockRequest(resources, client);
 IComponents components = new MockComponents(resources);
 $ = SetupFactory.create(ITestSetup.class).getSetup(client, resources, assets, request, pages, components, new MockJspTags(resources, pages), new MockServices());
 }
  • 43. Initialize Unit Test Context public abstract class UnitTestBase {
 
 public static final String MANDANT_TESTS = “/content/foobar”;
 public static final Locale TEST_LOCALE = new Locale("ko");
 public ITestSetup $;
 
 @Before
 public void setUpTestContext() throws Exception {
 Locale.setDefault(TEST_LOCALE);
 IAemClient client = new MockAemClient();
 IResources resources = new MockResources(client);
 IPages pages = new MockPages(resources, client);
 IAssets assets = new MockAssets(resources, client);
 IRequest request = new MockRequest(resources, client);
 IComponents components = new MockComponents(resources);
 $ = SetupFactory.create(ITestSetup.class).getSetup(client, resources, assets, request, pages, components, new MockJspTags(resources, pages), new MockServices());
 }
  • 44. Development Cycle with AEM New Integrated Test Copy Test to UnitTests Adapt Mocks Test-drive New Feature
  • 45. Write Acceptance Test first @Test public void testIsShowTitleBelowWithImageAndSizeNormalAndPositionLeftReturnsTrue() { Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content"); Resource asset = $.anAsset("/content/dam/quatico-test/test-image.jpg"); Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage", "text", "<b>hello</b>"); $.anImage(contentPage.getPath() + "/jcr:content/textimage/image", FILE_REFERENCE, asset.getPath(), "imageSize", NORMAL, "imagePosition", LEFT_ABOVE); TextImageController testObj = new TextImageController().setup( $.aPageContextWithComponent(target, contentPage)); assertTrue(testObj.isShowTitleBelow()); }
  • 46. TDD away, with Unit Tests @Test public void testIsShowTitleBelowWithImagePresentAndSizeNormalAndPositionLeftReturnsTrue() throws Exception { Resource asset = $.anAsset("/content/dam/foo.jpg"); Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>"); $.anImage(“/a/b/c/jcr:content/textimage/image", FILE_REFERENCE, asset.getPath(), "imageSize", NORMAL, "imagePosition", LEFT_ABOVE); assertTrue(aTextImageController(target).isShowTitleBelow()); } @Test public void testIsShowTitleBelowWithImagePresentAndSizeSmallAndPositionLeftReturnFalse() throws Exception { Resource asset = $.anAsset("/content/dam/foo.jpg"); Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>"); $.anImage("/a/b/c/jcr:content/textimage/image", FILE_REFERENCE, asset.getPath(), "imageSize", SMALL, "imagePosition", LEFT_ABOVE); assertFalse(aTextImageController(target).isShowTitleBelow()); } @Test public void testIsShowTitleBelowWithNoImagePresentReturnsFalse() throws Exception { Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>"); assertFalse(aTextImageController(target).isShowTitleBelow()); } @Test public void testValidateWithValidTextResourceYieldsOk() throws Exception { Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>"); assertStatusEquals(Ok(), aTextImageController(target).validate()); } @Test public void testValidateWithEmptyTextResourceYieldsWarning() throws Exception { Resource target = aTextImage("/a/b/c"); Status actual = aTextImageController(target).validate(); assertTrue(actual.isWarning()); }
  • 47. Unit Testing with AEM Mock AEM JCR foo() : Foo bar() : Bar zip: Zip zap : Zap TextImage testFooWithNull() testFooWithOk() testBarWithEmpty() testBarWithResult() TextImageTest Client
  • 48. Unit Testing with AEM Round trip: ~5000 unit tests ~30s Mock AEM JCR foo() : Foo bar() : Bar zip: Zip zap : Zap TextImage testFooWithNull() testFooWithOk() testBarWithEmpty() testBarWithResult() TextImageTest Client
  • 49. Conclusion: No excuses • Best practices for initializing, stubbing and mocking objects are made consistently available • Test setups cost virtually nothing • You write more simple, reliable, maintainable tests • Listen to their feedback more frequently • New developers quickly adapt to best practices
  • 50. Yes, it’s possible UT Performance Testing Integration Testing Human Testing End-To-End Testing HT Integration Testing Performance Testing Unit Testing End-To-End Testing