Testing Services

§Testing Services

§Running tests

You can run tests from the Activator console.

  • To run all tests, run test.
  • To run only one test class, run testOnly followed by the name of the class i.e. testOnly my.namespace.MyTest.
  • To run only the tests that have failed, run testQuick.
  • To run tests continually, run a command with a tilde in front, i.e. ~testQuick.

§JUnit

The recommended test framework for Lagom is JUnit

import static org.junit.Assert.*;

import org.junit.Test;

public class SimpleTest {

  @Test
  public void testSum() {
    int a = 1 + 1;
    assertEquals(2, a);
  }

  @Test
  public void testString() {
    String str = "Hello world";
    assertFalse(str.isEmpty());
  }

}

§Dependency

To use this feature add the following in your project’s build.

<dependency>
    <groupId>com.lightbend.lagom</groupId>
    <artifactId>lagom-javadsl-testkit_2.11</artifactId>
    <version>${lagom.version}</version>
    <scope>test</scope>
</dependency>

In sbt:

libraryDependencies += lagomJavadslTestKit

When using Cassandra the tests must be forked, which is enabled by adding the following in your project’s build:

.settings(lagomForkedTestSettings: _*)

§How to test one service

Lagom provides support for writing functional tests for one service in isolation. The service is running in a server and in the test you can interact with it using its service client, i.e. calls to the service API. These utilities are defined in ServiceTest.

import static com.lightbend.lagom.javadsl.testkit.ServiceTest.*;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class HelloServiceTest {

  @Test
  public void shouldSayHello() throws Exception {
    withServer(defaultSetup(), server -> {
      HelloService service = server.client(HelloService.class);

      String msg = service.sayHello().invoke("Alice").toCompletableFuture().get(5, SECONDS);
      assertEquals("Hello Alice", msg);
    });
  }

}

Dependencies to other services must be replaced by stub or mock implementations by overriding the bindings of the GuiceApplicationBuilder in the Setup. If we are writing a test for the HelloService and that has a dependency to a GreetingService we must create an implementation of the GreetingService that can be used for the test without running the real GreetingService. Something like this:

static class GreetingStub implements GreetingService {
  @Override
  public ServiceCall<String, String> greeting() {
    return req -> CompletableFuture.completedFuture("Hello");
  }
}

private final Setup setup = defaultSetup()
    .withConfigureBuilder(b -> b.overrides(
        bind(GreetingService.class).to(GreetingStub.class)));

Note how the dependency is overridden when constructing the test Setup object, which then can be used as parameter to the withServer method instead of the defaultSetup() in the above HelloServiceTest.

The server is by default running with persistence, pubsub and cluster features enabled. Cassandra is also started before the test server is started. If your service does not use these features you can disable them in the Setup, which will reduce the startup time.

Disable persistence, including Cassandra startup:

private final Setup setup1 = defaultSetup().withPersistence(false);

If cluster is disabled the persistence is also disabled, since cluster is a prerequisite for persistence. Disable cluster and pubsub:

private final Setup setup2 = defaultSetup().withCluster(false);

There are two different styles that can be used when writing the tests. It is most convenient to use withServer as illustrated in the above HelloServiceTest. It automatically starts and stops the server before and after the given lambda.

When your test have several test methods, and especially when using persistence, it is faster to only start the server once in a static method annotated with @BeforeClass and stop it in a method annotated with @AfterClass.

import static com.lightbend.lagom.javadsl.testkit.ServiceTest.*;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class AdvancedHelloServiceTest {

  private static TestServer server;

  @BeforeClass
  public static void setUp() {
    server = startServer(defaultSetup().withCluster(false));
  }

  @AfterClass
  public static void tearDown() {
    if (server != null) {
      server.stop();
      server = null;
    }
  }

  @Test
  public void shouldSayHello() throws Exception {
    HelloService service = server.client(HelloService.class);
    String msg = service.sayHello().invoke("Alice").toCompletableFuture().get(5, SECONDS);
    assertEquals("Hello Alice", msg);
  }

  @Test
  public void shouldSayHelloAgain() throws Exception {
    HelloService service = server.client(HelloService.class);
    String msg = service.sayHello().invoke("Bob").toCompletableFuture().get(5, SECONDS);
    assertEquals("Hello Bob", msg);
  }

}

§How to test several services

Lagom will provide support for writing integration tests that involve several interacting services. This feature is not yet implemented.

§How to test streamed request/response

Let’s say we have a service that have streaming request and/or response parameters. For example an EchoService like this:

public interface EchoService extends Service {

  ServiceCall<Source<String, NotUsed>, Source<String, NotUsed>> echo();

  default Descriptor descriptor() {
    return named("echo").withCalls(
      namedCall("echo", this::echo)
    );
  }
}

When writing tests for that the Akka Streams TestKit is very useful. We use the Streams TestKit together with the Lagom ServiceTest utilities:

import static com.lightbend.lagom.javadsl.testkit.ServiceTest.*;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.Arrays;
import org.junit.Test;
import akka.NotUsed;
import akka.stream.javadsl.Source;
import akka.stream.testkit.TestSubscriber.Probe;
import akka.stream.testkit.javadsl.TestSink;

public class EchoServiceTest {

  @Test
  public void shouldEchoStream() throws Exception {
    withServer(defaultSetup().withCluster(false), server -> {
      EchoService service = server.client(EchoService.class);

      // Use a source that never terminates (concat Source.maybe) so we
      // don't close the upstream, which would close the downstream
      Source<String, NotUsed> input =
          Source.from(Arrays.asList("msg1", "msg2", "msg3"))
          .concat(Source.maybe());
      Source<String, NotUsed> output = service.echo().invoke(input)
          .toCompletableFuture().get(5, SECONDS);
      Probe<String> probe = output.runWith(TestSink.probe(server.system()),
          server.materializer());
      probe.request(10);
      probe.expectNext("msg1");
      probe.expectNext("msg2");
      probe.expectNext("msg3");
      probe.cancel();
    });
  }

}

Read more about it in the documentation of the Akka Streams TestKit.

§How to test PersistentEntity

Persistent Entities can be used in the service tests described above. In addition to that you should write unit tests using the PersistentEntityTestDriver, which will run the PersistentEntity without using a database.

This is described in the documentation of Persistent Entity

Found an error in this documentation? The source code for this page can be found here. Please feel free to edit and contribute a pull request.