Functional testing Java-EE applications with TestEE.fi – Part 4: Cucumber JVM

The previous posts in this series introduced how TestEE.fi makes it really easy to test the functional behavior of your Java-EE applications with JUnit 4. While JUnit surely is the most common Java framework to write and execute your tests with, it’s far from being the only one. One at least equally interesting framework for Behavior Driven Development (BDD) that TestEE.fi integrates nicely with is Cucumber JVM.

Note: In this installment of our series I assume you already know how to use Cucumber JVM. If you don’t and want to, this tutorial provides a nice starting point. If you’re not in to BDD at the moment, you can safely skip this installment.

The project

Cucumber is a tool for testing the actual behavior of your application (hence the term Behavior Driven Development). So for the sake of this blog post it only makes sense to look at it on the foundation of an actual sample application.

For demonstrating BDD with Cucumber in TestEE.fi we’re going to implement some important pieces of a little hypothetical messaging application. You’ll be able add messages, they will be persisted in a database and additionally newly added messages will also be (hypothetically) posted on twitter. And since we’re professionals, we’re following architectural best practices (which will help us with the testing later – as it should!), so we’ll implement things as a classic 3-tier application.

Have a look at the following collaboration diagram that presents the overall structure of the application we will be implementing.

Note: the entire source code used in this installment is available as a working example here.

In this architectural diagram you can see a classical 3-tier application with a SQL database for persistence and access to an external system for publishing messages on twitter. Let’s have a look at the general purpose and implementation details of the classes involved here (the blue ones).

MessageFacade

Exposes the following business use-cases to the presentation layer and performs transformation between the external representation of the use-case and the internal representation of the application.

@Singleton
public class MessageFacade {
    @EJB
    private MessageService messageService;

    public void addMessage(final String messageText) {
        messageService.addMessage(new Message(
                System.currentTimeMillis(),
                messageText
        ));
    }

    public Set<String> getMessages() {
        return messageService.getAllMessages().stream()
                .map(Message::getText)
                .collect(toSet());
    }
}
MessageFacade

As you can see we’re implementing our project using EJBs. Also note that we’re using a business entity named Message which is instantiated in this facade method – it is implemented as a simple JPA POJO. Once a new Message entity is created from the incoming message text, further processing is delegated to the MessageService.

The second operation transforms all messages retrieved via the MessageService to Strings and hands them up upon request.

MessageService

Coordinates interaction between the persistence layer, external systems, and exposes the resulting behavior to the MessageFacade.

@Singleton
public class MessageService {
    @EJB
    private MessageDao messageDao;
    @EJB
    private TwitterAdapter twitterAdapter;

    public void addMessage(final Message message) {
        twitterAdapter.publishOnTwitter(message.getText());
        messageDao.persist(message);
    }

    public Set<Message> getAllMessages() {
        return messageDao.getAll();
    }
}
MessageService

No surprises here: as we’ve seen in the architecture diagram, the MessageService requires the MessageDao for storing and retrieving messages and the TwitterAdapter to forward the added messages to twitter. The code elegantly reflects these two operations involved in the business use case.

The second operation is to hand up all messages found by the DAO upon request – no additional business operations have to be performed here.

MessageDao

Encapsulates access to the SQL database for storing and retrieving messages.

@Singleton
public class MessageDao {
    @PersistenceContext(unitName = "testUnit")
    private EntityManager entityManager;

    public void persist(final Message message) {
        entityManager.persist(message);
    }

    public Set<Message> getAll() {
        final TypedQuery<Message> query = entityManager.createQuery("SELECT m FROM Message m", Message.class);
        return new HashSet<>(query.getResultList());
    }
}
MessageDao

The MessageDao implements storage and retrieval of Message entities using JPA. Therefore it has the EntityManager for the persistence context with the unit name “testUnit” injected and operates on it to communicate with the database.

TwitterAdapter

Encapsulates access to the external Twitter system. We will be mocking this for our testing purposes, so no actual implementation will be provided (which would be far out of scope anyway), so all we see here is a simple stub method:

@Singleton
public class TwitterAdapter {
    public void publishOnTwitter(final String message) {
        // Code to publish on twitter
    }
}
TwitterAdapter

Testing our application with Cucumber and TestEE.fi

Now that we have the basic business logic established it’s time to look at the tests. I intentionally left out the presentation layer in this example application, since we’ll be testing our use-cases starting with the facade – not an uncommon pattern for functional testing.

There’s a number of anatomical differences between a JUnit 4 test setup and one for Cucumber JVM. Contrary to  JUnit’s class-driven approach, Cucumber JVM tests are driven by feature files and backed by step classes that provide the “glue” between the textual execution descriptions in the feature files and the actual java code to be executed.

We’ll first take a quick look at the scenarios we want to test and then dive into the glue code, which is where TestEE.fi integrates seamlessly with Cucumber-JVM.

Scenarios to test

Since we’re only exhibiting simple application functionality in this example we’ll only have two scenarios in one feature file here:

Feature: Messaging

  Scenario: Adding a message
    Given the message "Hello world" already exists
    When I add the message "TestEE.fi rocks"
    Then the following messages should be available
      | Hello world     |
      | TestEE.fi rocks |
    
  Scenario: Publishing on twitter
    When I add the message "TestEE.fi rocks"
    Then the message "TestEE.fi rocks" was published to twitter
    And no other message was published to twitter
messaging.feature

The first scenario assumes the precondition that we already have an existing entry in our database. When we add a new one and then retrieve the known messages, then we expect to see both the old and the new one.

The second scenario doesn’t care about any prior entries. It only checks that, once you add a new message, it also gets published on Twitter.

TestEE.fi integrated “glue code”

Before we dive into the actual implementation of our “glue code” I want to take a minute and reflect on our testing strategy here:

  • We’ll be testing the behavior of the application by accessing the Facade. For test data setup (the existing database entry) I’ll be directly using the MessageDao, since it exposes the required functionality without further side effects to production code.
  • Since we surely don’t want to test against the actual Twitter service we’ll have to mock the external service. Mocking the TwitterAdapter is the natural candidate here – this is where our clean architecture pays of.
  • We’ll need an in-memory database and a schema set up for testing, so our persistence layer has something to work with.

Before we can use TestEE.fi’s Cucumber-JVM integration in our tests, we have to add the correct dependency to our test runtime.

Running Cucumber JVM from a Maven build is usually achieved using a little trick: Cucumber JVM uses a JUnit 4 Runner that leverages the JUnit 4 runtime to execute Cucumber tests. For this to work you need to introduce a JUnit 4 test class that applies that very Runner – you can then also add some runtime options (usually passed via command line) using the @CucumberOptions annotation. While a detailed explanation of this is out of scope for this article (and can be found here) I’ll give you a quick example how this looks.

Note: this is not part of the example source code available on github since that is built with Gradle.

@RunWith(Cucumber.class)
@CucumberOptions(glue = "steps")
public class TestRunner {
}
Example of a test runner class for Cucumber JVM

In this example we’re also specifying the name of the package where our steps (“glue”) are located using the @CucumberOptions annotation. Once you have such a test runner class it’s merely a matter of providing the right dependencies in your pom.xml and Maven already executes your Cucumber JVM tests during the build:

<dependencies>
    <!-- TestEE.fi with Cucumber integration -->
    <dependency>
        <groupId>fi.testee</groupId>
        <artifactId>testeefi-cucumber-all</artifactId>
		<version>0.6.1</version>
        <scope>test</scope>
    </dependency>
	
    <!-- Logging implementation - TestEE.fi uses slf4j -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
		<version>1.1.7</version>
        <scope>test</scope>
    </dependency>
	
    <!-- JUnit 4 is required for implementing the test runner -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
	
    <!-- The Cucumber-JVM to JUnit bridge -->
    <dependency>
        <groupId>info.cukes</groupId>
        <artifactId>cucumber-junit</artifactId>
        <version>1.2.5</version>
        <scope>test</scope>
    </dependency>
</dependencies>
Running TestEE.fi integrated Cucumber-JVM tests from Maven

The Gradle setup is quite a bit simpler (and as usual a lot less verbose than a pom.xml) since it skips the JUnit 4 detour and simply executes Cucumber-JVM via its build-in command line interface.

dependencies {
	compile 'javax:javaee-api:7.0'
		
	// TestEE.fi with Cucumber integration
	testCompile 'fi.testee:testeefi-cucumber-all:0.6.1'
		
	// Logging implementation - TestEE.fi uses slf4j
	testCompile 'ch.qos.logback:logback-classic:1.1.7'
		
	// asserts and matchers
	testCompile 'org.hamcrest:hamcrest-all:1.3'
}

// Execute Cucumber as part of the "test" task
test << {
    javaexec {
        main = "cucumber.api.cli.Main"
        classpath = configurations.testRuntime + sourceSets.main.output + sourceSets.test.output
        args = ['--plugin', 'pretty', '--glue', 'steps', 'src/test/resources']
    }
}
Running TestEE.fi integrated Cucumber-JVM tests from Gradle

@CucumberSetup

Having read the previous installments of this series you already know how all this would work in a JUnit based test: you’d be using annotations for wiring an in-memory test database, have it set up using Flyway and simply provide a @Mock annotated field for the TwitterAdapter which will then be used automagically for dependency injection by TestEE.fi where suitable.

Well, surprise: the same applies to TestEE.fi’s Cucumber-JVM integration. Here you write a special class for setting up what you need (exactly as you would do on your JUnit 4 test class!) and additionally annotate it with @CucumberSetup – that’s the place where you can define your annotations and mocks. You can also use a @PostConstruct method of the cucumber setup class to set up any default behavior for your mocks and it is also the correct location for setting up your test data via a static @TestData method.

Note: Make sure that for any given cucumber test project you only have exactly one @CucumberSetup annotated class in your classpath. If it’s missing or multiple classes with this annotation are found on the classpath, TestEE.fi will refuse to run your tests and fail with an exception instead.

Since the test setup for our little messaging application is rather simple, so is the cucumber setup class for our tests:

@CucumberSetup
@TestDataSource(name = "jdbc/testDataSource", factory = H2PostgresConnectionFactory.class)
@Flyway(dataSource = "jdbc/testDataSource")
public class TestSetup {
    @Mock
    private TwitterAdapter twitterAdapter;
}
@CucumberSetup

As you can see, the cucumber setup in our case is purely declarative. We state that we need a database, that it’s set up with Flyway (I’ll omit the migration – it’s dead simple and can be found here). We also specify that instead of an actual bean instance of TwitterAdapter we want a mock injected where suitable.

Now that our test environment is set up, we’ll finally take a look at the actual step classes with the “glue code”.

Messaging steps

I’ve decided to split the “glue code” in to two classes: one for steps concerning messages, and another one for steps related to publishing on Twitter. Splitting your step classes based on what they interact with limits the number of concerns you will be implementing in them and is a pattern I highly recommend when writing Cucumber step classes.

As already stated, the first “glue code” class we’re looking at concerns itself with the messaging aspect of our scenarios.

public class MessageSteps {
    @EJB
    private MessageDao messageDao;
    @EJB
    private MessageFacade messageFacade;

    @Given("^the message \"([^\"]*)\" already exists$")
    public void messageAlreadyExists(final String messageText) {
        messageDao.persist(new Message(1, messageText));
    }

    @When("^I add the message \"([^\"]*)\"")
    public void addMessage(final String messageText) {
        messageFacade.addMessage(messageText);
    }

    @Then("^the following messages should be available$")
    public void checkMessage(final DataTable table) {
        final HashSet<String> expected = new HashSet<>(table.asList(String.class));
        final Set<String> actual = messageFacade.getMessages();
        assertThat(actual, is(equalTo(expected)));
    }
}
MessageSteps

Apart from the step methods annotated with Cucumber’s @Given, @When and @Then annotations for step matching, you can see how we’re accessing our application’s business logic the same way we would if we were using JUnit: we simply use the @EJB annotation to have TestEE.fi inject the beans we require to implement our tests into our step class instance, and from there we can simply write our step classes using those injected EJBs.

The second step class is even simpler and works on the same mechanics, but with a little twist.


public class TwitterSteps {
    // Always use @EJB when accessing mocks, so CDI doesn't inject a proxy instead of the actual mock
    @EJB
    private TwitterAdapter twitterAdapter;

    @Then("^the message \"([^\"]*)\" was published to twitter$")
    public void theMessageWasPublishedToTwitter(final String message) {
        verify(twitterAdapter).publishOnTwitter(message);
    }

    @Then("^no other message was published to twitter$")
    public void noOtherMessageWasPublishedToTwitter() throws Throwable {
        verifyNoMoreInteractions(twitterAdapter);
    }
}
TwitterSteps

Note how the TwitterAdapter is injected with the @EJB annotation and how there is no @Mock annotation this time. That’s because the @Mock annotation only has meaning in the cucumber setup class. Everywhere else (including the step classes) the mock is injected using standard dependency injection mechanisms.

Nevertheless it is still a mock: it’s exactly the mock instance that was initialized in your cucumber setup class. Consequently all the operations you can perform on mocks (such as verify(), mock() etc. – I’m using Mockito here) work perfectly and are used in the step implementations.

Transactional behavior

One other topic I should talk about a bit is the transactional behavior of TestEE.fi when running Cucumber-JVM tests. You surely remember from our last installment how, for JUnit, the basic idea is to setup one single database for an entire test class and then roll back the transaction of each test method after it’s finished.

Since Cucumber doesn’t have a concept comparable to a test method, the transactional behavior of TestEE.fi differs here, but only a little. The database is created, initialized and set up with @TestData once for all Features and Scenarios in the test run’s classpath while each Scenario rolls back to that initial state after it’s run.

Think about it: this relates directly to how JUnit 4 tests are executed, since the database is set up up once for your cucumber setup class (the equivalent of your JUnit 4 test class where you declare your environment via annotations)  and then rolled back after each test, which in Cucumber JVM’s case corresponds to a Scenario.

The detailed transactional behavior thus looks like this:

  1. Create the database
  2. Setup schema (with Flyway or Liquibase)
  3. Setup test data (in @TestData method)
  4. Commit
  5. Run scenario
  6. Rollback
  7. Repeat steps 5 & 6 until all scenarios have run
  8. Delete the database

Conclusion

While Cucumber is a fundamentally different way of structuring, writing and running your tests, TestEE.fi makes it remarkably easy and very similar to the JUnit 4 way you’re already used to. All the features you’ve seen in the previous examples are available in exactly the same way!

This closes installment #4 of this blog series on TestEE.fi. The working sample code this installment is based on can be found here.

If you want to know more about advanced testing with TestEE.fi, be sure to check out the other posts of this series to get you up to speed quickly:

Leave a Reply

Your email address will not be published. Required fields are marked *