Jackalope is an in-memory implementation of the JCR with stubbing capabilities for Apache Sling.
The goal of Jackalope is to better enable unit testing JCR representations of complex objects structures. Java objects can normally be simply represented in the JCR as nodes with properties.
Simple mocking of the underlying JCR and Sling interfaces can be used to unit test reading and writing these kinds of objects. However, when object aggregations like Sling component structures need to be represented, the inability of simple mock objects to manage state become a real limitation.
The obvious solution is to use a JCR implementation that uses memory as its storage. There are, in fact, memory-based JCR implementations -- including one that is distributed with the Jackrabbit project. (Jackrabbit is the reference implementation for the JCR JSR.) All of these, however, require far too much in setup and resources to be useful for unit testing. They are designed to be long running, multi-threaded processes.
Our solution to this problem is to develop a simple and fast JCR implementation that can be used in unit tests. It does not implement the complete JCR spec, but just the parts that we've needed, including the basic facilities for reading and writing repository workspaces.
There is another great project from Citytech called Prosper, which was released after Jackalope was written internally at Time Warner Cable. Each project has its own set of features, but the primary difference between Jackalope and Prosper is that Prosper uses Groovy string-based metaprogramming for building its trees, which has all the advantages and disadvantages of a fully dynamic API. Choose the one that best your needs/style: Choice is good. :-)
The 3.x line targets AEM 6.x, whereas the 2.x versions target AEM 5.6.
Versioning follows the Semantic Versioning standard
The library is composed of 3 main packages.
This is the primary interface that should be used by clients of the library.
The JCRBuilder
class contains factory methods that can be used to create a virtual repository for use in test cases.
To bring this class's methods in, typically you would do:
import static JCRBuilder.repostory
import static JCRBuilder.node
import static JCRBuilder.property
This package contains the classes that implement the primary Sling API classes like Resource
and ResourceResolver
.
Users may want to use SimpleResourceResolverFactory
to inject into services and servlets.
This package contains the classes that implement the primary JCR API classes like Node
and Property
.
given:
def repository = repository(
node("content",
node("test1",
node("callingrates",
node("intl-direct-dial",
property("sling:resourceType", "admin/components/content/callingratetable"),
node("france",
property("sling:resourceType", "admin/components/content/callingrate"),
property("additional-minute-rate", "0.60"))))))).build()
def resolver = new SimpleResourceResolverFactory(repository).administrativeResourceResolver
def resource = resolver.getResource("/content/test1/callingrates/intl-direct-dial")
when:
def callingRateTable = new CallingRateTable(resource)
then:
callingRateTable.getRate("france")
callingRateTable.getRate("france").additionalMinuteRate == "0.60"
The resource resolver factory can be used to test servlets and services by injection.
def repository = repository(
node("content",
node("test1",
node("callingrates",
node("intl-direct-dial",
property("sling:resourceType", "admin/components/content/callingratetable"),
node("france",
property("sling:resourceType", "admin/components/content/callingrate"),
property("additional-minute-rate", "0.60"))))))).build()
def servlet = new CallingRatesImportServlet(new SimpleResourceResolverFactory(repository))
Some classes are designed to read and write node trees and do not require the full repository implementation. These classes can build and use nodes directly.
def node = node("content",
node("test1",
node("callingrates",
node("intl-direct-dial",
property("sling:resourceType", "admin/components/content/callingratetable"),
node("france",
property("sling:resourceType", "admin/components/content/callingrate"),
property("additional-minute-rate", "0.60"))))))).build()
Queries are also supported. A JCRQueryBuilder is used to create a query manager that associates queries with fixed result sets that can be used for testing.
def node = node("result").build()
JCRQueryBuilder.queryManager(node.session, query("query", "language", result(node))).build()
when:
def queryResult = node.session.workspace.queryManager.createQuery("query", "language").execute()
then:
queryResult.nodes.hasNext()
when:
def results = Lists.newArrayList(queryResult.nodes)
then:
results[0] == node
Jackalope uses gradle as its build system:
./gradlew build
Jackalope can be used by including the following in your build files (assuming Gradle):
repositories {
maven {
url "http://dl.bintray.com/twcable/aem"
}
}
testCompile 'com.twcable.jackalope:jackalope:3.0.2'
For the developers of Jackalope, please read the documentation for creating and releasing a new version.
Copyright 2015 Time Warner Cable, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.