Skip to content

Unit testing

DuyHai DOAN edited this page Mar 27, 2021 · 16 revisions

Test Resources

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

  1. 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

  2. createAndUseKeyspace(String keyspaceName): create the given keyspace (SimpleStrategy) upon server startup and connect to it.

  3. entityClassesToTruncate(Class<?>...entityClassesToTruncate): truncate tables used by given entity classes

  4. tablesToTruncate(String...tablesToTruncate): truncate given tables

  5. truncateBeforeTest(): self-explanatory

  6. truncateAfterTest(): self-explanatory

  7. truncateBeforeAndAfterTest(): self-explanatory. DEFAULT value

  8. 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

  9. withProtocolVersion(ProtocolVersion.V4): set the protocol version for the Java driver. By default the protocol version is set to ProtocolVersion.V4

The StatementsCache is provided to avoid re-creating a new instance of StatementsCache for each test class and re-preparing the same statements.


Usage

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.

Native session

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

Script executor

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

Bootstrap from AchillesTestResource

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

Standalone instance

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("...");

Script Templates

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)

Home

Clone this wiki locally