{"id":21710495,"url":"https://github.com/quatico-solutions/aem-testing","last_synced_at":"2025-04-12T17:30:48.300Z","repository":{"id":59219703,"uuid":"67228416","full_name":"quatico-solutions/aem-testing","owner":"quatico-solutions","description":"A unified testing library for AEM development.","archived":false,"fork":false,"pushed_at":"2016-09-27T00:49:46.000Z","size":102,"stargazers_count":5,"open_issues_count":0,"forks_count":1,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-26T11:51:08.291Z","etag":null,"topics":["aem","apache-sling","builder-methods","tdd"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/quatico-solutions.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-09-02T14:16:31.000Z","updated_at":"2018-08-28T02:08:04.000Z","dependencies_parsed_at":"2022-09-13T18:20:49.135Z","dependency_job_id":null,"html_url":"https://github.com/quatico-solutions/aem-testing","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quatico-solutions%2Faem-testing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quatico-solutions%2Faem-testing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quatico-solutions%2Faem-testing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quatico-solutions%2Faem-testing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/quatico-solutions","download_url":"https://codeload.github.com/quatico-solutions/aem-testing/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248604954,"owners_count":21132073,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aem","apache-sling","builder-methods","tdd"],"created_at":"2024-11-25T23:15:57.978Z","updated_at":"2025-04-12T17:30:48.279Z","avatar_url":"https://github.com/quatico-solutions.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AemTesting Library\n\nA library for efficient creation of unit and integration tests in the AEM technology stack.\nThe library provides creation methods for JCR resources that can be easily customized \nto create your components, pages, assets, tags and services for your web application.\n\nSmart builders allow for concise test setups with valid default values. Direct service injection\nprovides your services as mocked or actual implementation. Standard Sling adapter \nregistration makes custom implementations available.\n\n\n## Getting Started\n\n1. Provide a `UnitTestBase` class as parent of all your unit tests.\nThis class provides the testing API to your tests and binds it to the ``$`` character.  \n\t ```java\n\t public class UnitTestBase { \n\t\tprotected ITestSetup $;\n\t\tprivate IAemClient client; \n\t\t\n\t\t@Before \n\t\tpublic void setUpTestContext() throws Exception {     \n\t\t\tthis.client = new AemClient(Type.Unit).startUp();  // Type.Integration for integrated tests\n\t\t\tIResources resources = new Resources(client);     \n\t\t\t$ = SetupFactory.create(ITestSetup.class).getSetup(\n\t\t\t\t\tclient, \n\t\t\t\t\tresources, \n\t\t\t\t\tnew Pages(client), \n\t\t\t\t\tnew Components(resources, client),                                \n\t\t\t\t\tnew Assets(client)); \n\t\t} \n\t\t\n\t\t@After public void tearDownTestContext() throws Exception { \n\t\t\tthis.client.shutDown();   \n\t\t}\n\t}\n\t ```\nHere we create an ``AemClient`` with a ``ResourceResolverType``. Currently there are two valid \noptions: ``Type.Unit`` and ``Type.Integration``. We create a ``ITestSetup`` with  creation methods \nfor ``Resources``, ``Pages``, ``Components`` and ``Assets``. Skip to \n[How to create your own testing API](How to create your own testing API) if you want to see how to \nadd your own project specific implementors.\n\n2. Now with the test setup available, let's add a first unit test. For example to test some Java \ncode that needs a JCR resource on a page, create your test class and extend `UnitTestBase`. Then \ncall your API as follows: \n\t```java\n\tpublic class TextImageControllerTest extends UnitTestBase {\n\t@Test public void isShowTitleBelowWithImagePresentAndSizeSmallAndPositionLeftReturnFalse() throws Exception {   \n\t\tResource page   = $.aPage(\"/content/test/ko/home/page\");   \n\t\tResource target = $.aResource(page, \"/jcr:content/foobar\", \"text\", \"\u003cb\u003ehello\u003c/b\u003e\");     \n\t\t\n\t\t// your code using the resources comes here followed by assertions\n\t}\n\t```\n\n\n## How to create your own testing API\n\n1. Let's add our project specific creation/builder methods: We want to add API to easily create the \nresource `TeaserConfiguration`.\n\t```java\n\tpublic class AppComponents extends Components implements IAppComponents { \n\t\tAppComponents(IResources resources, IAemClient client) {\n\t\t\tthis.resources = resources;\n\t\t\tthis.client = client;\n\t\t}\n\t\tpublic Resource aTeaserConfiguration(Resource parent, TeaserType type, Object... p) {  \n\t\t\tString configPath = type != null ? type.getConfigPath() : PageTeaser.PATH_EXTENSION;  \n\t\t\tProperties props = new Properties(p).append(\"sling:resourceType\", ComponentType.TEASER_CONFIGURATION);\n\t\t\treturn resources.aResource(parent, configPath, props.toArray());\n\t\t}\n\t}\n\t```\n\n2. Now you want to inherit from `IAppComponent` in `ITestSetup`.\n\t```java\n\tpublic interface ITestSetup extends IAemClient, IResources, IPages, IAppComponents, IAssets {}\n\t```\n\n3. Finally extend your `UnitTestBase` so the new method can be called directly by \n`$.aTeaserConfiguration(...)` within the tests.\n\t```java\n\t...\n\tIResources resources = new Resources(client);\n\t$ = SetupFactory.create(ITestSetup.class).getSetup(\n\t\t\tclient, \n\t\t\tresources, \n\t\t\tnew Pages(client),                                \n            new Assets(client), \n            new AppComponents(resources, client)); \n\t...\n\t```\n\t\nMake sure to follow some basic conventions to stay consistent with the provided API:\n* Creation/builder methods that start with `a...` take following arguments: **Parent resource, \nrelative path (name of resource) and properties**.\n  ```java\n  public Resource aText(Resource parent, String relative, Object... properties) throws Exception;\n  ```\n* Creation/builder methods that go with the structure `a...WithParent` take following arguments: *Absolute path, properties*.\n  ```java\n  public Resource aConfigPageWithParents(String path, Object... properties) throws Exception;\n  ```\n* Creation/builder methods that take no arguments return `AbstractBuilder\u003cT\u003e`.\n  ```java\n  ComponentContextBuilder aComponentContext();\n  ```\n\n\n## Details on builders, services and adapters\n\n\n### Build your own builder\nAemTesting makes it easy to add your own builder implementations. \n\n1. Simply extend `AbstractBuilder\u003cT\u003e` with your builder class that builds `T`. Let's create an AssetBuilder:\n\t```java\n\tpublic class AssetBuilder extends AbstractBuilder\u003cAsset\u003e {\n\t\tpublic AssetBuilder() {\n\t\t\tsuper(Asset.class);\n\t\t}\n\t\t\n\t\t@Override\n\t\tprotected Asset internalBuild() throws Exception {\n\t\t\tresult = // create your mocked or actual asset implementation\n\t\t    return result;\n\t\t}\n\t}\n\t```\n\tMake sure to override `internalBuild()` because that is where we build our `Asset` eventually.\n\n2. We want to create an `Asset` with **path** and a **mimetype**. The latter is an optional property \nthus we provide a default value. A builder can be implemented as follows:\n\t```java\n\tpublic class AssetBuilder extends AbstractBuilder\u003cAsset\u003e {\n        public AssetBuilder(IAemClient client) {\n       \t    this.client = client;\n       \t    mimetype(\"image/jpeg\");\n        }\n\t\tpublic String path() {\n            return getValue(\"path\", String.class);\n        }\n        \n        public AssetBuilder path(String value) {\n            return addValue(\"path\", value);\n        }\n        \n        public String mimetype() {\n            return getValue(\"mimetype\");\n        }\n        \n        public AssetBuilder mimetype(String value) {\n            return addValue(\"mimetype\", value);\n        }\n       \n         ...\n        \n        @Override\n        protected Asset internalBuild() throws Exception {\n       \t\tContentBuilder content = client.getContentBuilder();\n       \t\tthis.result = content.asset(path, classpathResource(), mimetype(), metadata());\n      \t\treturn result;\n        }\n    }\n\t```\n\tMake sure to set all optional values in the constructor in case the setters are never called.\n\t\n\n### Provide your own services\nUse the following API from IAemClient to create mocked or actual service implementations and \ninject them into your container.\n```java\n\u003cR\u003e R require(Class\u003cR\u003e serviceClass, Object... properties) throws Exception;\n\n\u003cR\u003e R require(InjectedService\u003cR\u003e service, Object... properties) throws Exception;\n\n\u003cR\u003e R require(IServiceBuilder\u003cR\u003e builder, Object... properties) throws Exception;\n\n\u003cR, C extends R\u003e InjectedService\u003cR\u003e service(Class\u003cR\u003e type, C service, InjectedService\u003c?\u003e... requiredServices) throws Exception;\n\n\u003cR\u003e InjectedService\u003cR\u003e service(R service, InjectedService\u003c?\u003e... requiredServices) throws Exception;\n\n\u003cR\u003e InjectedService\u003cR\u003e service(IServiceBuilder\u003cR\u003e builder, InjectedService\u003c?\u003e... requiredServices) throws Exception;\n```\nThe following tests demonstrate how the API can be used:\n```java\n@Test\npublic void viewGetTitleWithoutAnyTitleReturnsDefaultTitle() throws Exception {\n\tResource page = $.anAdminPageWithParents(\"/content/amtliche-mitteilungen/app/detail\");\n\t$.require(anAppService($.aNoteService().build()));\n\t$.aRequest().parent($.getRequest()).resource(page).params(PARAM_ID, ID_VALUE_NEW).build();\n\t\n\tAdminPageController testObj = anAdminPageController(page);\n\t\n\tassertEquals(StringUtils.EMPTY, testObj.getView().getTitle());\n}\n\nprivate InjectedService\u003cAppService\u003e anAppService(NoteService noteService) throws Exception {\n    InjectedService\u003cAppService\u003e appService = $.service(\n            AppService.class, new AppServiceImpl().setNoteService(noteService),\n            $.service(ResourceResolverFactory.class, $.getResourceResolverFactory())\n    );\n    appService.\u003cAppServiceImpl\u003egetInstance().activate($.aComponentContext().build());\n    return appService;\n}\n```\nWe create an injectable service of `AppService.class` with `$.service(...)`. It is injected into the\ntest container with `$.require(...).`\n\n\n### Provide your own adapters\nThe mechanism for providing own adapters is basically the same as in [Sling](https://sling.apache.org/), \nit is surfaced in the `IAemClient` the the following two methods:\n```java\n\u003cT1, T2\u003e void registerAdapter(Class\u003cT1\u003e adaptableClass, Class\u003cT2\u003e adapterClass, Function\u003cT1,T2\u003e adaptHandler);\n\t\n\u003cT1, T2\u003e void registerAdapter(Class\u003cT1\u003e adaptableClass, Class\u003cT2\u003e adapterClass, T2 adapter);\n```\n\n\n## General Requirements\nAemTesting is build on top of AemMock:\n\n* AEM 6.2\n* JDK 1.8 \n* Maven \u003e=3.2.5\n* Supports AemMock 1.7.0, Sling-Mock 2.0.0\n\n\n### Installation and configuration\n1. Clone the repo.\n2. Run ``mvn clean install`` in the project root folder. \n3. Include the artifact as dependency into your AEM project:\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.quatico.base\u003c/groupId\u003e\n    \u003cartifactId\u003eaem-testing\u003c/artifactId\u003e\n    \u003cversion\u003e0.2.2\u003c/version\u003e\n    \u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n```\nRead up on [How to build AEM projects using Maven](https://docs.adobe.com/docs/en/cq/5-6-1/developing/developmenttools/how-to-build-aem-projects-using-apache-maven.html) to get started with Maven.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquatico-solutions%2Faem-testing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fquatico-solutions%2Faem-testing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquatico-solutions%2Faem-testing/lists"}