Baratine on GitHub

JUnit Testing

Introduction

This tutorial shows how to test Baratine services with JUnit.

Baratine provides a set of classes that help testing Baratine Services.

The classes are located in package com.caucho.junit and include the following:

Main classes:

  • RunnerBaratine class is a JUnit runner used non–web services testing
  • WebRunnerBaratine class is a JUnit runner used web services testing
  • ServiceTest specifies service to deploy with one of the JUnit runners above
  • ConfigurationBaratine configures test environment such as workDir and test clock
  • Http configures host and port for WebRunnerBaratine

Utility classes:

  • HttpClient is used to make http requests; should be used with WebRunnerBaratine
  • State accumulates state; convenience utility testing Timers, Events and Pipes
  • TestTime controls test clock, effectively jumping forward by specified interval

Basic Service

Since Baratine services are designed to be isolated from each other, they naturally fit into independent unit tests. When possible, Baratine services should be designed as leaf services with few dependencies, which makes them easier to test.

The JUnit tests can focus on the sequential logic for the service because Baratine’s ensures in-order message queues. If your application has a buggy or unusual timing sequence, the JUnit tests can replicate it precisely.

To start you will need your Baratine implementation class (annotated with @Service) and a matching interface for the service proxy. Let’s start with an echo service.

The service below echos its method argument:

package junit.echo;

import io.baratine.service.Result;
import io.baratine.service.Service;

/**
 * Implementation of the echo service.
 */

@Service
public class EchoImpl implements Echo
{
  @Override
  public void echo(String message, Result<String> result)
  {
    result.ok(message);
  }
}

The echo interface will become a client proxy to the service. For test convenience, the interface includes blocking versions of the method calls. The blocking methods are also useful for calling the service from a blocking environment. The non-blocking version is used by other Baratine services:

package example;

import io.baratine.service.Result;

/**
 * Proxy interface to the echo service.
 */
public interface Echo
{
  void echo(String message, Result<String> result);
}

The JUnit test for EchoImpl creates a Baratine environment as if the echo service was deployed as in production. The test class select services to be tested; any others are not deployed. The proxy is looked up with injection. (It’s possible to programmatically lookup the proxy from the ServiceManager.)

The JUnit test for EchoImpl might look like the following:

package junit.echo;

import javax.inject.Inject;

import com.caucho.junit.RunnerBaratine;
import com.caucho.junit.ServiceTest;
import io.baratine.service.ResultFuture;
import io.baratine.service.Service;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

//Specifies JUnit Runner to be used with this unit test
@RunWith(RunnerBaratine.class)

//Deploys EchoImpl service
@ServiceTest(EchoImpl.class)
public class EchoTest
{
  //Injects service with a matching type (matched by interface in this case)
  @Inject
  @Service
  Echo _echo;

  @Test
  public void test()
  {
    final String message = "Hello World!";
    final ResultFuture<String> result = new ResultFuture<>();

    //make a call to the service
    _echo.echo(message, result);

    Assert.assertEquals(message, result.get());
  }
}

Vault Service, Persistence

Vault Service provides managed persistence for Assets (see vault/#vault-services for details on Baratine Asset / Vault model)

JUnit Test:

package test;

import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

import com.caucho.junit.*;
import io.baratine.service.*;
import io.baratine.vault.*;
import org.junit.*;
import org.junit.runner.RunWith;

//Specifies JUnit Runner for this test case
@RunWith(RunnerBaratine.class)

//Deploys service specified by a class
@ServiceTest(TestServiceVault.EntityVault.class)
public class TestServiceVault
{
  //Note that vault parameter will be injected
  @Test
  public void test(@Service EntityVault vault)
  {
    ResultFuture<IdAsset> id = new ResultFuture<>();

    vault.create("Hello World!", id);

    Assert.assertEquals("DVS1aMAAR3I", id.get(1, TimeUnit.SECONDS).toString());

    ResultFuture<Entity> e = new ResultFuture<>();

    vault.findByValue("Hello World!", e);

    Assert.assertEquals("DVS1aMAAR3I: Hello World!",
                        e.get(1, TimeUnit.SECONDS).asString());
  }

  //Vault and Asset definition will normally be defined as standalone
  //classes. This test example inlines them for convenience.
  @Service
  public interface EntityVault extends Vault<IdAsset,Entity>
  {
    void create(String value, Result<IdAsset> id);

    void findByValue(String value, Result<Entity> result);
  }

  @Asset
  public static class Entity
  {
    @Id
    private IdAsset id;
    private String value;

    @Modify
    public void create(String value, Result<IdAsset> id)
    {
      this.value = value;
      id.ok(this.id);
    }

    public String asString()
    {
      return id + ": " + value;
    }
  }
}

Web accessible Service

A test for a service accessible via http should be executed with WebRunnerBaratine JUnit Runner.

Exposing method to GET requests is achieved by annotating method with @Get annotation.

(Baratine provides annotations for all other HTTP methods as well).

Unit Test Code:

package web;

import java.io.IOException;

import com.caucho.junit.*;
import io.baratine.service.*;
import io.baratine.web.*;
import org.junit.*;
import org.junit.runner.RunWith;

//Specify appropriate JUnit Runner
@RunWith(WebRunnerBaratine.class)

//Specify service to deploy
@ServiceTest(EchoTest.Echo.class)
@Http(port = 8080)
public class EchoTest
{
  //The client is injected using configuration from Http annotation on the class
  @Test
  public void test(HttpClient client) throws IOException
  {
    HttpClient.Response response = client.get("/echo?v=Hello+World!").go();

    Assert.assertEquals("Hello World!", response.body());
  }

  //Service definition will normally be in a stand alone class. This example
  //inlines for convenience.
  @Service
  public static class Echo
  {
    @Get
    public void echo(@Query("v") String value, Result<String> result)
    {
      result.ok(value);
    }
  }
}

Scheduled Service, Timer

Timers allow scheduling method calls. Testing a sheduled service requires test method to control the clock. This is achieved with TestTime class which can fast forward the clock by specified interval.

The assertion should be checking that action specified by the scheduled method actually takes effect.

In the example below the action simply adds a String to shared state represented by Baratine provided State utility class:

package plain;

import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

import com.caucho.junit.*;
import io.baratine.service.*;
import io.baratine.timer.Timers;
import org.junit.*;
import org.junit.runner.RunWith;

//Specify JUnit Runner to use with this test
@RunWith(RunnerBaratine.class)
//Specify service to deploy
@ServiceTest(TimerTest.ScheduledService.class)
public class TimerTest
{
  @Test
  public void test(@Service("timer:") Timers timer) throws InterruptedException
  {
    //add a second and check that timer did not fire
    TestTime.addTime(1, TimeUnit.SECONDS);
    Thread.sleep(100);
    Assert.assertEquals("", State.state());

    //add two seconds and check that timer did fire
    TestTime.addTime(2, TimeUnit.SECONDS);
    Thread.sleep(100);

    Assert.assertEquals("\n  fire!", State.state());
  }

  @Service
  //specifies that Service should be started after deployment
  @Startup
  public static class ScheduledService
  {
    @Inject
    @Service("timer:")
    Timers timers;

    //OnInit method will run on startup of the service
    @OnInit
    public void init()
    {
      timers.runAfter(this::fire, 2, TimeUnit.SECONDS, Result.ignore());
    }

    private void fire(Cancel cancel)
    {
      State.add("\n  fire!");
    }
  }
}

Example Code on GitHub

Examples on testing various Baratine services with JUnit can be found at a link below: https://github.com/baratine/examples-junit