{"id":21972035,"url":"https://github.com/icfnext/prosper","last_synced_at":"2025-04-28T12:22:33.281Z","repository":{"id":7379443,"uuid":"8706706","full_name":"icfnext/prosper","owner":"icfnext","description":"A Spock-based integration testing library for prosperous AEM development.","archived":false,"fork":false,"pushed_at":"2020-02-27T16:27:08.000Z","size":1696,"stargazers_count":28,"open_issues_count":4,"forks_count":19,"subscribers_count":52,"default_branch":"develop","last_synced_at":"2025-03-30T09:31:33.733Z","etag":null,"topics":["aem","groovy","jcr","mock","sling","spock"],"latest_commit_sha":null,"homepage":"","language":"Groovy","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/icfnext.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":"2013-03-11T15:38:17.000Z","updated_at":"2024-03-31T15:13:58.000Z","dependencies_parsed_at":"2022-09-26T21:51:11.454Z","dependency_job_id":null,"html_url":"https://github.com/icfnext/prosper","commit_stats":null,"previous_names":["citytechinc/prosper"],"tags_count":53,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icfnext%2Fprosper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icfnext%2Fprosper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icfnext%2Fprosper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icfnext%2Fprosper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/icfnext","download_url":"https://codeload.github.com/icfnext/prosper/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251311584,"owners_count":21569062,"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","groovy","jcr","mock","sling","spock"],"created_at":"2024-11-29T15:00:56.128Z","updated_at":"2025-04-28T12:22:33.263Z","avatar_url":"https://github.com/icfnext.png","language":"Groovy","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Prosper\n\n[ICF Next](http://www.icfnext.com/)\n\n## Overview\n\nProsper is an integration testing library for Adobe Experience Manager projects using [Spock](http://docs.spockframework.org/), a [Groovy](http://www.groovy-lang.org/)-based testing framework notable for it's expressive specification language.  The library contains a base Spock specification that provides an in-memory JCR, Sling framework support, and a mock OSGi context for registering and testing services.\n\n## Features\n\n* Test AEM projects outside of an OSGi container in the standard Maven build lifecycle.\n* Write test specifications in [Groovy](http://www.groovy-lang.org/) using [Spock](http://docs.spockframework.org/), a JUnit-based testing framework with an elegant syntax for writing tests quickly and efficiently.\n* Sling `ResourceResolver` instance backed by an in-memory Jackrabbit Oak JCR instance supporting the complete set of repository operations.\n* Supplies mock OSGi bundle and [Sling](https://sling.apache.org/documentation/development/sling-mock.html) contexts for registering services, adapters, and Sling models.\n* Utilizes Groovy builders from the [AEM Groovy Extension](https://github.com/icfnext/aem-groovy-extension) to provide a simple DSL for creating test content.\n* Provides additional builders for mock Sling requests and responses to simplify setup of test cases.\n\n## Requirements\n\n* Maven 3.x\n* Familiarity with Groovy language and the Spock specification syntax (or see included tests for examples).\n\n## Compatibility\n\nProsper Version(s) | AEM Version(s)\n------------ | -------------\n14.x.x | 6.3, 6.4, 6.5\n13.x.x, 12.x.x | 6.4\n11.x.x | 6.3\n10.x.x, 9.x.x, 8.x.x | 6.2\n7.x.x, 6.x.x, 5.x.x, 4.x.x | 6.1\n3.x.x, 2.x.x, 1.x.x | 6.0\n\u003c= 0.10.x | 5.6 (CQ)\n\n## Getting Started\n\nAdd Maven dependency to project `pom.xml`.\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.icfolson.aem.prosper\u003c/groupId\u003e\n    \u003cartifactId\u003eprosper\u003c/artifactId\u003e\n    \u003cversion\u003e15.0.0\u003c/version\u003e\n    \u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\nCreate a `src/test/groovy` directory in your project structure and add a Spock specification extending the base `ProsperSpec`.\n\n```groovy\nimport com.icfolson.aem.prosper.specs.ProsperSpec\n\nclass ExampleSpec extends ProsperSpec {\n\n    def setupSpec() {\n        // use PageBuilder from base spec to create test content\n        pageBuilder.content {\n            home(\"Home\") {\n                \"jcr:content\"(\"sling:resourceType\": \"foundation/components/page\")\n            }\n        }\n    }\n\n    // basic content assertions provided\n    def \"home page exists\"() {\n        expect:\n        assertPageExists(\"/content/home\")\n    }\n\n    // Node metaclass provided by AEM Groovy Extension simplifies JCR operations\n    def \"home page has expected resource type\"() {\n        setup:\n        def contentNode = session.getNode(\"/content/home/jcr:content\")\n\n        expect:\n        contentNode.get(\"sling:resourceType\") == \"foundation/components/page\"\n    }\n}\n```\n\nConfigure Groovy compiler and Surefire plugin in Maven `pom.xml`.  Additional configurations details for projects with mixed Java/Groovy sources can be found [here](https://github.com/groovy/groovy-eclipse/wiki/Groovy-Eclipse-Maven-plugin).\n\n```xml\n\u003cbuild\u003e\n    \u003csourceDirectory\u003esrc/main/groovy\u003c/sourceDirectory\u003e\n    \u003ctestSourceDirectory\u003esrc/test/groovy\u003c/testSourceDirectory\u003e\n    \u003cplugins\u003e\n        \u003cplugin\u003e\n            \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n            \u003cversion\u003e3.8.0\u003c/version\u003e\n            \u003cconfiguration\u003e\n                \u003ccompilerId\u003egroovy-eclipse-compiler\u003c/compilerId\u003e\n                \u003csource\u003e1.8\u003c/source\u003e\n                \u003ctarget\u003e1.8\u003c/target\u003e\n                \u003cencoding\u003eutf-8\u003c/encoding\u003e\n            \u003c/configuration\u003e\n            \u003cdependencies\u003e\n                \u003cdependency\u003e\n                    \u003cgroupId\u003eorg.codehaus.groovy\u003c/groupId\u003e\n                    \u003cartifactId\u003egroovy-eclipse-compiler\u003c/artifactId\u003e\n                    \u003cversion\u003e2.9.2-01\u003c/version\u003e\n                \u003c/dependency\u003e\n                \u003cdependency\u003e\n                    \u003cgroupId\u003eorg.codehaus.groovy\u003c/groupId\u003e\n                    \u003cartifactId\u003egroovy-eclipse-batch\u003c/artifactId\u003e\n                    \u003cversion\u003e2.4.3-01\u003c/version\u003e\n                \u003c/dependency\u003e\n            \u003c/dependencies\u003e\n        \u003c/plugin\u003e\n        \u003cplugin\u003e\n            \u003cartifactId\u003emaven-surefire-plugin\u003c/artifactId\u003e\n            \u003cversion\u003e2.19.1\u003c/version\u003e\n            \u003cconfiguration\u003e\n                \u003cincludes\u003e\n                    \u003c!-- Spock naming convention.  Change as needed if your test classes have a different naming strategy. --\u003e\n                    \u003cinclude\u003e**/*Spec*\u003c/include\u003e\n                \u003c/includes\u003e\n            \u003c/configuration\u003e\n        \u003c/plugin\u003e\n    \u003c/plugins\u003e\n\u003c/build\u003e\n\n\u003cdependencies\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.codehaus.groovy\u003c/groupId\u003e\n        \u003cartifactId\u003egroovy-all\u003c/artifactId\u003e\n        \u003cversion\u003e2.4.15\u003c/version\u003e\n    \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n\nFinally, run `mvn test` from the command line to verify that specifications are found and execute successfully.\n\n## User Guide\n\n### Specification Anatomy\n\nThe [Spock documentation](http://spockframework.github.io/spock/docs/1.0/spock_primer.html) outlines the features and methods that define a Spock specification (and their JUnit analogues, for those more familiar with Java-based testing), but the `setupSpec` fixture method is of critical importance when testing AEM classes.  This method, executed prior to the first feature method of the specification, is the conventional location for creating test content in the JCR.  Likewise, the `cleanup` and `cleanupSpec` fixture methods are the appropriate place to remove test content following the execution of a test (or set of tests).  However, the `cleanupSpec` method will be implemented less frequently, as the base `ProsperSpec` removes all test content from the JCR after every specification is executed to prevent cross-contamination of content between specifications.\n\n```groovy\nclass ExampleSpec extends ProsperSpec {\n\n    def setupSpec() {\n        // runs before first feature method, create test content and initialize shared resources here\n    }\n\n    def setup() {\n        // runs before each feature method, less commonly used\n    }\n\n    def \"a feature method\"() {\n        setup:\n        // create test-specific content, instances and/or mocks\n\n        expect:\n        // result\n    }\n\n    def \"another feature method\"() {\n        setup:\n        // create test content, etc.\n\n        when:\n        // stimulus\n\n        then:\n        // response\n    }\n\n    def cleanup() {\n        // optionally call the method below to remove all test content after each feature method\n        removeAllNodes()\n    }\n\n    def cleanupSpec() {\n        // runs after all feature methods, implemented in base spec but less commonly used\n    }\n}\n```\n\n### Available Fields and Methods\n\nThe base specification exposes a number of fields and methods for use in tests.\n\nField Name | Type | Description\n:---------|:---------|:-----------\nsession | [javax.jcr.Session](https://docs.adobe.com/docs/en/spec/javax.jcr/javadocs/jcr-2.0/javax/jcr/Session.html) | Administrative JCR session\nresourceResolver | [org.apache.sling.api.resource.ResourceResolver](http://sling.apache.org/apidocs/sling7/org/apache/sling/api/resource/ResourceResolver.html) | Administrative Sling Resource Resolver\npageManager | [com.day.cq.wcm.api.PageManager](https://helpx.adobe.com/experience-manager/6-3/sites/developing/using/reference-materials/javadoc/com/day/cq/wcm/api/PageManager.html) | AEM Page Manager\nnodeBuilder | [com.icfolson.aem.groovy.extension.builders.NodeBuilder](http://icfnext.github.io/aem-groovy-extension/groovydocs/com/icfolson/aem/groovy/extension/builders/NodeBuilder.html) | JCR [Node Builder](https://github.com/Citytechinc/prosper#content-builders)\npageBuilder | [com.icfolson.aem.groovy.extension.builders.PageBuilder](http://icfnext.github.io/aem-groovy-extension/groovydocs/com/icfolson/aem/groovy/extension/builders/PageBuilder.html) | AEM [Page Builder](https://github.com/Citytechinc/prosper#content-builders)\nslingContext | [com.icfolson.aem.prosper.context.SlingContextProvider](https://sling.apache.org/documentation/development/sling-mock.html) | Prosper extension of Sling/OSGi Context\n\nSee the `ProsperSpec` [GroovyDoc](http://icfnext.github.io/prosper/groovydocs/com/icfolson/aem/prosper/specs/ProsperSpec.html) for details on available methods.\n\n### Content Builders\n\nA test specification will often require content such as pages, components, or supporting node structures to facilitate the interactions of the class under test.  Creating a large and/or complex content hierarchy using the JCR and Sling APIs can be tedious and time consuming.  The base `ProsperSpec` simplifies the content creation process by defining two Groovy [builder](http://groovy-lang.org/dsls.html#_builders) instances, `pageBuilder` and `nodeBuilder`, that greatly reduce the amount of code needed to produce a working content structure in the JCR.\n\n#### Page Builder\n\n`PageBuilder` creates nodes of type `cq:Page` by default.\n\n```groovy\ndef setupSpec() {\n    pageBuilder.content {\n        beer { // page with no title\n            styles(\"Styles\") { // create page with title \"Styles\"\n                \"jcr:content\"(\"jcr:lastModifiedBy\": \"mdaugherty\", \"jcr:lastModified\": Calendar.instance) {\n                    data(\"sling:Folder\")  // descendant of \"jcr:content\", argument sets node type instead of title\n                    navigation(\"sling:resourceType: \"components/navigation\", \"rootPath\": \"/content/beer\")\n                }\n                dubbel(\"Dubbel\")\n                tripel(\"Tripel\")\n                saison(\"Saison\")\n            }\n            // create a page with title \"Breweries\" and set properties on it's \"jcr:content\" node\n            breweries(\"Breweries\", \"jcr:lastModifiedBy\": \"mdaugherty\", \"jcr:lastModified\": Calendar.instance)\n        }\n    }\n}\n```\n\nNodes named `jcr:content`, however, are treated as unstructured nodes to allow the creation of descendant component/data nodes that are not of type `cq:Page`.  Descendants of `jcr:content` nodes can specify a node type using a `String` value as the first argument in the tree syntax (see `/content/beer/styles/jcr:content/data` in the above example).  As with page nodes, additional properties can be passed with a map argument.\n\n#### Node Builder\n\nThis builder should be used when creating non-page content hierarchies, such as descendants of `/etc` in the JCR.  The syntax is similar to `PageBuilder`, but the first argument is used to specify the node type rather than the `jcr:title` property.\n\n```groovy\ndef setupSpec() {\n    nodeBuilder.etc { // unstructured node\n        designs(\"sling:Folder\") { // node with type\n            site(\"sling:Folder\", \"jcr:title\": \"Site\", \"inceptionYear\": 2014) // node with type and properties\n        }\n    }\n}\n```\n\nThe above example will create an `nt:unstructured` (the default type) node at `/etc` and `sling:Folder` nodes at `/etc/designs` and `/etc/designs/site`.  An additional map argument will set properties on the target node in the same manner as `PageBuilder`.\n\nBoth builders automatically save the underlying JCR session after executing the provided closure.\n\nIn addition to the content builders, the [session](https://docs.adobe.com/content/docs/en/spec/jsr170/javadocs/jcr-2.0/javax/jcr/Session.html) and [pageManager](https://helpx.adobe.com/experience-manager/6-3/sites/developing/using/reference-materials/javadoc/com/day/cq/wcm/api/PageManager.html) instances provided by the base specification can be used directly to create test content in the JCR.\n\n### Content Import\n\nAnother way to generate supporting content is to import a vault exported/packaged content structure.  The content import is completely automatic and will run for all of your specs when it detects content to import.  To take advantage of the content import, simply create a `SLING-INF/content` directory within your project's test resources location (ex. `src/test/resources/SLING-INF/content`).  The `content` directory must contain a child `jcr_root` directory and `META-INF` directory.  The `jcr_root` directory will contain all the vault exported/packaged content.  The `META-INF` directory will contain all the vault configuration XML files typically found within an AEM package.\n\n#### Specifying a Filter File\n\nYou can specify an alternative `filter.xml` file by using the class level `com.icfolson.aem.prosper.annotations.ContentFilters` annotation.  Simply provide the path to the `filter.xml` file in the XML element and it will be used instead of the `filter.xml` file within the META-INF/vault directory.  The example below shows how you can provide a path to a non-default `filter.xml` file.\n\n```groovy\n@ContentFilters(\n    xml = \"/SLING-INF/content/META-INF/vault/alt-filter.xml\"\n)\nclass MySpec extends ProsperSpec {\n\n}\n```\n\n#### Dynamic Filters\n\nYou can also dynamically generate spec-specific filters by using the various content filter annotations.  This allows you to isolate the content you need for your individual specs.  The example below shows how you can define a dynamic filter.\n\n```groovy\n@ContentFilters(\n    filters = [\n        @ContentFilter(\n            root = \"/etc\",\n            mode = ImportMode.REPLACE,\n            rules = [\n                @ContentFilterRule(\n                    type = ContentFilterRuleType.EXCLUDE,\n                    pattern = \"/etc/tags\"\n                )\n            ]\n        )\n    ]\n)\nclass MySpec extends ProsperSpec {\n\n}\n```\n\nIt is also possible to extend the provided `filter.xml` file through dynamic filters.  This allows you to provide common filters in the XML file and define specific filters for your spec with the annotations.  The example below shows how you can extend an existing `filter.xml` file.\n\n```groovy\n@ContentFilters(\n    xml = \"/SLING-INF/content/META-INF/vault/alt-filter.xml\",\n    filters = [\n        @ContentFilter(\n            root = \"/etc\",\n            mode = ImportMode.REPLACE,\n            rules = [\n                @ContentFilterRule(\n                    type = ContentFilterRuleType.EXCLUDE,\n                    pattern = \"/etc/tags\"\n                )\n            ]\n        )\n    ]\n)\nclass MySpec extends ProsperSpec {\n\n}\n```\n\n#### Skipping Content Import\n\nIn some cases, you may not want to import content for your spec.  To skip the content import, annotate your spec class with `@SkipContentImport`.  The example below shows a spec that skips the content import.\n\n```groovy\n@SkipContentImport\nclass MySpec extends ProsperSpec {\n\n}\n```\n\n### Metaclasses\n\nThe [AEM Groovy Extension](https://github.com/icfnext/aem-groovy-extension) decorates the `com.day.cq.wcm.api.Page`, `javax.jcr.Node`, and `javax.jcr.Binary` classes with additional methods to simplify common operations.  See the extension library [Groovydocs](http://icfnext.github.io/aem-groovy-extension/groovydocs/com/icfolson/aem/groovy/extension/metaclass/GroovyExtensionMetaClassRegistry.html) for details of these additions.  The metaclasses are registered automatically and available for use in all test methods.\n\n### Assertions\n\nProsper's built-in assertion methods are used within Spock's `then` and `expect` blocks to verify the state of content in the transient repository following execution of a test.  For example, a test that creates a node with property values (either directly or as a side effect of other operations) will want to confirm that the node was created and that the desired property name and values exist in the JCR.\n\nSince expressions in these blocks are implicitly treated as boolean conditions by Spock, Prosper's assertion methods eliminate the need to logically combine expressions for the complex conditions required to assert JCR state.  This is best illustrated with an example.\n\n```groovy\nimport com.day.cq.commons.jcr.JcrConstants\nimport com.day.cq.wcm.api.NameConstants\n\ndef \"create content\"() {\n    setup: \"create a page with some properties\"\n    def pageProperties = [\"sling:resourceType\": \"foundation/components/page\",\n        \"jcr:description\": \"Prosper is an integration testing library for AEM.\"]\n\n    pageBuilder.content {\n        prosper(\"Prosper\") {\n            \"jcr:content\"(pageProperties)\n        }\n    }\n\n    expect: \"page is created and properties match expected values\"\n    session.nodeExists(\"/content/prosper\")\n    \u0026\u0026 session.getNode(\"/content/prosper\").primaryNodeType.name == NameConstants.NT_PAGE\n    \u0026\u0026 session.getNode(\"/content/prosper\").hasNode(JcrConstants.JCR_CONTENT)\n    \u0026\u0026 pageProperties.every { name, value -\u003e\n        session.getNode(\"/content/prosper\").getNode(JcrConstants.JCR_CONTENT).get(name) == value\n    }\n}\n```\n\nThankfully, the `expect` block can be simplified using an assertion method from the base `ProsperSpec`.\n\n```groovy\nexpect: \"page is created and properties match expected values\"\nassertPageExists(\"/content/prosper\", pageProperties)\n```\n\nAll available `assert...` methods are detailed in the Prosper [GroovyDocs](http://icfnext.github.io/prosper/groovydocs/com/icfolson/aem/prosper/specs/ProsperSpec.html).\n\n### Mocking Requests and Responses\n\nTesting servlets and request-scoped supporting classes require mocking the `SlingHttpServletRequest` and `SlingHttpServletResponse` objects.  The `RequestBuilder` and `ResponseBuilder` instances acquired through the base spec leverage Groovy closures to set the necessary properties and state on these mock objects with a minimal amount of initialization code.\n\nGiven a Sling servlet:\n\n```groovy\nimport groovy.json.JsonBuilder\nimport org.apache.sling.api.SlingHttpServletRequest\nimport org.apache.sling.api.SlingHttpServletResponse\nimport org.apache.sling.api.servlets.SlingAllMethodsServlet\n\nimport javax.jcr.Session\nimport javax.servlet.ServletException\n\nimport static com.google.common.base.Preconditions.checkNotNull\n\nclass TestServlet extends SlingAllMethodsServlet {\n\n    @Override\n    protected void doPost(SlingHttpServletRequest request,\n        SlingHttpServletResponse response) throws ServletException, IOException {\n        def path = checkNotNull(request.getParameter(\"path\"))\n        def selector = request.requestPathInfo.selectors[1]\n\n        def session = request.resourceResolver.adaptTo(Session)\n\n        def node = session.getNode(path)\n\n        node.setProperty(\"testProperty\", selector)\n\n        session.save()\n\n        new JsonBuilder([path: path, value: selector]).writeTo(response.writer)\n    }\n}\n```\n\nA Prosper specification for this servlet will use the request and response builders to create the necessary method arguments to simulate a POST request to the servlet and verify both the JCR content updates and the JSON output resulting from the servlet execution.\n\n```groovy\nclass ServletSpec extends ProsperSpec {\n\n    def setupSpec() {\n        nodeBuilder.content {\n            prosper()\n        }\n    }\n\n    def \"missing request parameter throws exception\"() {\n        setup:\n        def servlet = new TestServlet()\n\n        // requestBuilder and responseBuilder are inherited from the base spec\n        def request = requestBuilder.build()\n        def response = responseBuilder.build()\n\n        when:\n        servlet.doPost(request, response)\n\n        then:\n        thrown(NullPointerException)\n    }\n\n    def \"valid request parameter sets node property and returns JSON response\"() {\n        setup:\n        def servlet = new TestServlet()\n\n        def request = requestBuilder.build {\n            parameterMap = [path: \"/content/prosper\"]\n            selectors = [\"one\", \"two\"]\n            contentType = \"application/json\"\n        }\n\n        def response = responseBuilder.build()\n\n        when:\n        servlet.doPost(request, response)\n\n        then:\n        assertNodeExists(\"/content/prosper\", [testProperty: \"two\"])\n\n        and:\n        response.outputAsString == '{\"path\":\"/content/prosper\",\"value\":\"two\"}'\n    }\n}\n```\n\nThe mock request and response objects delegate to the [MockSlingHttpServletRequest](http://static.javadoc.io/org.apache.sling/org.apache.sling.testing.sling-mock/2.2.20/org/apache/sling/testing/mock/sling/servlet/MockSlingHttpServletRequest.html) and [MockSlingHttpServletResponse](http://static.javadoc.io/org.apache.sling/org.apache.sling.testing.sling-mock/2.2.20/org/apache/sling/testing/mock/sling/servlet/MockSlingHttpServletResponse.html) objects from the Sling Mocks framework.  The setter methods exposed by these classes are thus made available in the `build` closures for the request and response builders to assist in setting appropriate mock values for the class under test.\n\n### Sling Context\n\nSee the [Groovydoc](http://icfnext.github.io/prosper/groovydocs/com/icfolson/aem/prosper/context/SlingContextProvider.html) for complete details of the available service registration and additional context methods; specific Sling adaptable examples are provided below.\n\n#### AEM Context Builder\n\nOverride the `getAemContext()` method in a test specification to supply a custom Resource Resolver configuration or add context callbacks using the wcm.io [AemContextBuilder](https://wcm.io/testing/aem-mock/junit4/apidocs/io/wcm/testing/mock/aem/junit/AemContextBuilder.html).\n\n```groovy\nclass CustomContextSpec extends ProsperSpec {\n\n    @Override\n    AemContext getAemContext() {\n        new AemContextBuilder(ResourceResolverType.JCR_OAK)\n            .beforeSetUp(new AemContextCallback() {\n                @Override\n                void execute(AemContext context) throws Exception {\n                    context.registerService(new CustomService())\n                }\n            })\n            .resourceResolverFactoryActivatorProps([\"resource.resolver.mapping\": [\"/content/:/\", \"/-/\"] as String[]])\n            .build()\n    }\n\n    def \"test\"() {\n        \n    }\n}\n```\n\n#### OSGi Services\n\nOSGi services can be mocked (fully or partially) using Spock's [mocking API](http://docs.spockframework.org/en/latest/interaction_based_testing.html#creating-mock-objects).  Real service objects can also be used if all required dependency services are registered in the Sling context.\n\n```groovy\nimport com.day.cq.replication.ReplicationActionType\nimport com.day.cq.replication.ReplicationException\nimport com.day.cq.replication.Replicator\nimport groovy.util.logging.Slf4j\nimport org.apache.felix.scr.annotations.Reference\nimport org.apache.felix.scr.annotations.sling.SlingServlet\nimport org.apache.sling.api.SlingHttpServletRequest\nimport org.apache.sling.api.SlingHttpServletResponse\n\nimport javax.jcr.Session\nimport javax.servlet.ServletException\n\n@SlingServlet(paths = \"/bin/replicate/custom\")\n@Slf4j(\"LOG\")\nclass CustomReplicationServlet extends SlingAllMethodsServlet {\n\n    @Reference\n    Replicator replicator\n\n    @Override\n    protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws\n        ServletException, IOException {\n        def path = request.getParameter(\"path\")\n        def session = request.resourceResolver.adaptTo(Session)\n\n        try {\n            replicator.replicate(session, ReplicationActionType.ACTIVATE, path)\n        } catch (ReplicationException e) {\n            LOG.error(\"replication error\", e)\n        }\n    }\n}\n```\n\nThe Prosper specification for this servlet can then set a mocked `Replicator` instance and verify the expected [interactions](http://docs.spockframework.org/en/latest/interaction_based_testing.html) using the Spock syntax.  Alternatively, services can inject other service references using the `slingContext.registerInjectActivateService` methods.\n\n```groovy\ndef \"servlet with mock service\"() {\n    setup:\n    def servlet = new CustomReplicationServlet()\n    def replicator = Mock(Replicator)\n\n    slingContext.registerService(Replicator, replicator)\n    slingContext.registerInjectActivateService(servlet)\n\n    def request = requestBuilder.build {\n        parameters = [path: \"/content\"]\n    }\n\n    def response = responseBuilder.build()\n\n    when:\n    servlet.doPost(request, response)\n\n    then:\n    1 * replicator.replicate(_, _, \"/content\")\n}\n```\n\n#### Registering Adapters\n\nSpecs can register adapters in the Sling context by adding `AdapterFactory` instances or by providing mappings from adapter instances to closures that instantiate these instances from a `Resource`, `ResourceResolver` or `SlingHttpRequestServlet`.  Adapters will be registered with the mock `BundleContext` and their adaptables and adapters properties will be respected when an adapter is chosen.  Registered `AdapterFactory` instances will pull these properties from the XML metadata files located in the classpath at /OSGI-INF, or the adaptable/adapter properites can be explicitly specified in the method arguments.  Registered adapter closures will use the `Resource`, `ResourceResolver` or `SlingHttpRequestServlet` as the adaptables property and the adapter instance class as the adapters property.  The methods for registering adapters are illustrated in the examples below.\n\n```groovy\nclass ExampleAdapterFactory implements AdapterFactory {\n\n    @Override\n    \u003cAdapterType\u003e AdapterType getAdapter(Object adaptable, Class\u003cAdapterType\u003e type) {\n        AdapterType result = null\n\n        if (type == String) {\n            if (adaptable instanceof ResourceResolver) {\n                result = \"Hello.\"\n            } else if (adaptable instanceof Resource) {\n                result = \"Goodbye.\"\n            }\n        }\n\n        result\n    }\n}\n\nclass ExampleSpec extends ProsperSpec {\n\n    def setupSpec() {\n        // example adapter factory\n        slingContext.registerAdapterFactory(new ExampleAdapterFactory(),\n            [\"org.apache.sling.api.resource.ResourceResolver\", \"org.apache.sling.api.resource.Resource\"] as String[],\n            [\"java.lang.String\"] as String[])\n\n        // resource adapters\n        slingContext.registerResourceAdapter(Integer, { Resource resource -\u003e\n            resource.name.length()\n        })\n        slingContext.registerResourceAdapter(Map, { Resource resource -\u003e\n            resource.metadata\n        })\n\n        // resource resolver adapters\n        slingContext.registerResourceResolverAdapter(Integer, { ResourceResolver resourceResolver -\u003e\n            resourceResolver.searchPath.length\n        })\n        slingContext.registerResourceResolverAdapter(Node, { ResourceResolver resourceResolver -\u003e\n            resourceResolver.getResource(\"/\").adaptTo(Node)\n        })\n\n        // request adapters\n        slingContext.registerRequestAdapter(Integer, { SlingHttpServletRequest request -\u003e\n            request.pathInfo.length()\n        })\n    }\n\n    def \"resource is adaptable to multiple types\"() {\n        setup:\n        def resource = resourceResolver.getResource(\"/\")\n\n        expect:\n        resource.adaptTo(type) == result\n\n        where:\n        type    | result\n        Integer | 0\n        Map     | [:]\n    }\n\n    def \"resource resolver is adaptable to multiple types\"() {\n        expect:\n        resourceResolver.adaptTo(String) == \"Hello.\"\n        resourceResolver.adaptTo(Integer) == 0\n        resourceResolver.adaptTo(Node).path == \"/\"\n    }\n\n    def \"request is adaptable\"() {\n        expect:\n        requestBuilder.build {\n            path = \"/request/path.html\"\n        }.adaptTo(Integer) == 18\n    }\n}\n```\n\n#### Sling Models\n\nSling Model classes and injectors can also be registered in the Sling context for use in tests.  The `addModelsForPackage` method from the `SlingContextProvider` scans the given package for classes annotated with `@org.apache.sling.models.annotations.Model` and registers them in the mock bundle context, while the `registerInjector` allows for registration of custom Sling injectors.  A model instance can then be acquired by adapting from a Sling resource or request as shown below.\n\n```groovy\npackage com.icfolson.aem.prosper\n\nimport org.apache.sling.models.annotations.Model\nimport org.apache.sling.models.annotations.injectorspecific.Self\n\n@Model(adaptables = [Resource, SlingHttpServletRequest])\nclass ProsperModel {\n\n    @Self\n    Resource resource\n\n    String getPath() {\n        resource.path\n    }\n}\n```\n```groovy\nclass ProsperModelSpec extends ProsperSpec {\n\n    def setupSpec() {\n        pageBuilder.content {\n            prosper()\n        }\n    }\n\n    def \"adapt resource to model\"() {\n        setup:\n        slingContext.addModelsForPackage(\"com.icfolson.aem.prosper\")\n\n        def resource = getResource(\"/content/prosper\")\n        def model = resource.adaptTo(ProsperModel)\n\n        expect:\n        model.path == \"/content/prosper\"\n    }\n}\n\n```\n\n### Adding JCR Namespaces and Node Types\n\nMany of the common AEM, JCR, and Sling namespaces and node types are registered when the Prosper test repository is created.  Additional namespaces and node types may be added at runtime by annotating a test spec with the `@NodeTypes` annotation and supplying an array containing paths to classpath .cnd file resources.  For more information on the CND node type notation, see [Node Type Notation](http://jackrabbit.apache.org/node-type-notation.html) in the Apache Jackrabbit documentation.  An example of the annotation usage is presented below.\n\n```groovy\nimport com.icfolson.aem.prosper.annotations.NodeTypes\n\n@NodeTypes(\"SLING-INF/nodetypes/spock.cnd\")\nclass ExampleSpec extends ProsperSpec {\n\n}\n```\n\n### Traits\n\n[Traits](http://groovy-lang.org/objectorientation.html#_traits) are a Groovy language feature that can be utilized to \"mix in\" new functionality to test specs.  Custom traits can be defined to support domain-specific features.\n\n```groovy\nimport com.icfolson.aem.prosper.builders.RequestBuilder\nimport org.apache.sling.api.SlingHttpServletRequest\n\ntrait MobileRequestTrait {\n\n    abstract RequestBuilder getRequestBuilder()\n\n    SlingHttpServletRequest buildMobileRequest(Map\u003cString, Object\u003e parameters) {\n        requestBuilder.setSelectors([\"mobile\"]).setParameterMap(parameters).build()\n    }\n}\n```\n\nSpecs can then implement the trait to add the new functionality.  Note that the `getRequestBuilder()` abstract method does not have to be implemented by the new spec, since this spec (as seen below) already inherits the required method from `ProsperSpec`.  Traits can thus \"borrow\" functionality from the base `ProsperSpec` by defining abstract methods that match the corresponding method signatures in `ProsperSpec`.\n\n```groovy\nimport spock.lang.Shared\n\nclass MobileRequestTraitSpec extends ProsperSpec implements MobileRequestTrait {\n\n    def \"trait usage\"() {\n        setup:\n        def request = buildMobileRequest([:])\n\n        expect:\n        request.requestPathInfo.selectors[0] == \"mobile\"\n    }\n}\n```\n\n## References\n\n* [Prosper GroovyDocs](http://icfnext.github.io/prosper/groovydocs/index.html)\n* [Spock Documentation](http://spockframework.github.io/spock/docs/1.0/index.html)\n* [Groovy Documentation](http://www.groovy-lang.org/documentation.html)\n\n## Versioning\n\nFollows [Semantic Versioning](http://semver.org/) guidelines.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficfnext%2Fprosper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ficfnext%2Fprosper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficfnext%2Fprosper/lists"}