-
Notifications
You must be signed in to change notification settings - Fork 92
Unit testing
For TDD lovers, Achilles comes along with a JUnit rule to start an embedded Cassandra server in memory and bootstrap the framework.
The rule extends JUnit ExternalResource
and exposes the following methods:
public class AchillesTestResource
{
public Session getNativeSession();
public ScriptExecutor getScriptExecutor();
public ManagerFactory getManagerFactory();
}
To obtain a resource, you should use the AchillesTestResourceBuilder
which exposes the following methods
-
withScript(scriptLocation)
: provide a CQL script file, reachable in the classpath, to be executed upon the Embededed Cassandra server startup. This method is useful to create the appropriate schema for unit testing -
createAndUseKeyspace(String keyspaceName)
: create the given keyspace (SimpleStrategy) upon server startup and connect to it. -
entityClassesToTruncate(Class<?>...entityClassesToTruncate)
: truncate tables used by given entity classes -
tablesToTruncate(String...tablesToTruncate)
: truncate given tables -
truncateBeforeTest()
: self-explanatory -
truncateAfterTest()
: self-explanatory -
truncateBeforeAndAfterTest()
: self-explanatory. DEFAULT value -
public ManagerFactory build(BiFunction<Cluster, StatementsCache, ManagerFactory> managerFactoryBuilder)
: provide a bi-lambda function to build the manager factory given thecom.datastax.driver.core.Cluster
object and Achilles sharedStatementsCache
-
withProtocolVersion(ProtocolVersion.V4)
: set the protocol version for the Java driver. By default the protocol version is set toProtocolVersion.V4
The StatementsCache
is provided to avoid re-creating a new instance of StatementsCache
for each test class and
re-preparing the same statements.
Below is a sample code to demonstrate how to use Achilles rules
public class MyTest
{
@Rule
public AchillesTestResource<ManagerFactory> resource = AchillesTestResourceBuilder
.forJunit()
.withScript("script1.cql")
.withScript("script2.cql")
.tablesToTruncate("users", "tweet_lines") // entityClassesToTruncate(User.class, TweetLine.class)
.createAndUseKeyspace("unit_test")
.
...
.build((cluster, statementsCache) -> ManagerFactoryBuilder
.builder(cluster)
.doForceSchemaCreation(true)
.withStatementCache(statementsCache) //MANDATORY
.withDefaultKeyspaceName("achilles_embedded")
.build()
);
private User_Manager userManager = resource.getManagerFactory().forUser();
@Test
public void should_test_something() throws Exception
{
...
userManager
.crud()
.insert(...)
...
}
}
In the log file, if the logger ACHILLES_DML_STATEMENT
is activated, we can see:
DEBUG ACHILLES_DML_STATEMENT - TRUNCATE users
DEBUG ACHILLES_DML_STATEMENT - Query ID b3bc7f3c-d232-4d43-8014-96d08b90804c : [INSERT INTO achilles_embedded.user(id,login,firstname,lastname) VALUES (:id,:login,:firstname,:lastname) USING TTL :ttl;] with CONSISTENCY LEVEL [ONE]
DEBUG ACHILLES_DML_STATEMENT - Java bound values : [271273866878694400, doanduyhai, DuyHai, DOAN]
DEBUG ACHILLES_DML_STATEMENT - Encoded bound values : [271273866878694400, doanduyhai, DuyHai, DOAN]
DEBUG ACHILLES_DML_STATEMENT - ResultSet[ exhausted: true, Columns[]]
DEBUG ACHILLES_DML_STATEMENT - Query ID b3bc7f3c-d232-4d43-8014-96d08b90804c results :
DEBUG ACHILLES_DML_STATEMENT - TRUNCATE users
Please notice the TRUNCATE users
query issued before and after the test for clean up.
By calling getNativeSession()
upon the resource, you can access directly the
com.datastax.driver.core.Session
object of the native Java driver. It may be
useful for triggering manual CQL statement to assert the test results
For some tests, we may insert test fixtures before executing the test itself. It can be done programmatically using
the native session
object but it can become quickly cumbersome if you have many data to insert.
For those scenarios, Achilles exposes a ScriptExecutor
than will handle CQL script execution for you.
The script executor also support BATCH statements in your CQL scripts
Example:
@Rule
public AchillesTestResource resource = ...
private Session session = resource.getNativeSession();
private ScriptExecutor scriptExecutor = resource.getScriptExecutor();
@Test
public void should_create_user() throws Exception {
//Given
scriptExecutor.executeScript("insert_user.cql");
//When
...
//Then
...
}
In the above example, the script executor takes a string as input, pointing to a CQL script file on the classpath.
As per Maven convention, it is recommended to put all your test CQL script into the src/test/resources
folder
You can also create a stand-alone ScriptExecutor instance. The constructor of the ScriptExecutor
class only requires
a com.datastax.driver.core.Session
object
final Session session = ...;
final ScriptExecutor executor = new ScriptExecutor(session);
executor.executeScript("...");
You can also make script templates and inject bound values at runtime. Let's say we have the following insert_user.cql
script:
INSERT INTO users(id, login, firstname, lastname) VALUES(${id}, ${login}, ${firstname}, ${lastname});
All the values are parameterized so that at runtime, we can call this template and inject the values:
@Test
public void should_create_user() throws Exception {
//Given
Map<String, Object> values = new HashMap<>();
values.put("id", RandomUtils.nextLong(0L, Long.MAX_VALUE));
values.put("login", "doanduyhai");
values.put("firstname", "DuyHai");
values.put("lastname", "DOAN");
//Inject the values into the template to generate the final CQL script
scriptExecutor.executeScriptTemplate("insert_user.cql", values);
//When
...
//Then
...
}
The script executor exposes other useful methods to let you execute arbitrary CQL statement as plain text or as Statement object:
/**
* Execute plain text CQL statement and return a native ResultSet
*/
public ResultSet execute(String statement);
/**
* Execute a CQL statement and return a native ResultSet
*/
public ResultSet execute(Statement statement);
/**
* Execute asynchronously a plain text CQL statement and return a future of native ResultSet
*/
public AchillesFuture<ResultSet> executeAsync(String statement);
/**
* Execute asynchronously a CQL statement and return a future of native ResultSet
*/
public AchillesFuture<ResultSet> executeAsync(Statement statement)
-
Bootstraping Achilles at runtime
- Runtime Configuration Parameters
-
Manager
-
Consistency Level
-
Cassandra Options at runtime
-
Lightweight Transaction (LWT)
-
JSON Serialization
-
Interceptors
-
Bean Validation (JSR-303)