§Testing Services
§Running tests
You can run tests from the sbt 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 pubsub, cluster and persistence features disabled. You may want to enable cluster in the Setup
:
private final Setup setup2 = defaultSetup().withCluster(true);
If your service needs persistence you will need to enable it explicitly. Cassandra Persistence requires clustering, so when you enable Cassandra, cluster will also be enabled automatically. Enable Cassandra Persistence:
private final Setup setup1 = defaultSetup().withCassandra(true);
There’s no way to explicitly enable or disable pubsub. When cluster is enabled (either explicitly or transitively via enabling Cassandra), pubsub will be available.
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 tests 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 broker publishing and consuming
The section on Message Broker Testing is related to Testing Services but is specific to testing the production and consumption via Brokers.
§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