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 the com.datastax.driver.core.Cluster object
and Achilles shared StatementsCache
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 thecom.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
AchillesTestResourceExample:
@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)