What’s the problem? Testcases in Lagom.
So, You want to write better unit and integration test cases then You have got read this blog. Alright, Let’s see what it takes to test your code in Lagom. In this blog I will be taking up different scenarios and try to explain How to write test cases for that particular scenario.
Testing in Lagom
Testing in Lagom is not a rocket science. However, It is a little different the way we test our code in Scala and Java. We will be using Junit framework(recommended by Lagom) and along with this we will be using lagom’s default testkit dependency. In order to test a service in Lagom we will basically be starting up a fake server which will make calls to our services and produce the result so that we could assert it against the expected result. Also, with fake server you have a flexibility to use embedded cassandra and cluster. You can set embedded cassandra to true whenever you need it, similarly clustering can also be set to true
For example your code needs cassandra the you will say withCassandra(true), bydefault it is false. We will see this in detail when we will cover all the scenarios which require cassandra to be true.
Alright, So Let’s get started
-
Basic Junit Test
Writing a basic Junit test-case is very easy.
Prerequisites
- Add Junit maven dependency
junit junit 4.11 test |
@Test public void testSum() { int a = 1 + 1 ; assertEquals( 2 , a); } |
2. Lagom Service Unit Test
Writing a Lagom Service Unit Test-case is very ease, All you need to do is to run a test-server in @BeforeClass and then tear down that server in @AfterClass
Prerequisites
1.Add maven dependency for writing lagom service testcases in POM.XML
Maven Dependency
< dependency > < groupId >com.lightbend.lagom</ groupId > < artifactId >lagom-javadsl-testkit_2.11</ artifactId > < version >${lagom.version}</ version > < scope >test</ scope > < exclusions > < exclusion > < groupId >junit</ groupId > < artifactId >junit</ artifactId > </ exclusion > </ exclusions > </ dependency > |
Sample Test-Case
public class WelcomeServiceTest { private static ServiceTest.TestServer server;
if (server != null ) { server.stop(); server = null ; } } @Test public void shouldWelcome() throws Exception { WelcomeService welcomeService = server.client(WelcomeService. class ); String actualResult = welcomeService.welcome().invoke(). toCompletableFuture().get( 20 , SECONDS); String expectedResult = "Welcome to MyService!" ; assertEquals(expectedResult, actualResult); } } |
3. Lagom Stream Test-Case
public class EchoServiceTest { @Test public void shouldEchoStream() throws Exception { withServer(defaultSetup().withCassandra( true ), 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( 20 , SECONDS); Probe probe = output.runWith(TestSink.probe(server.system()), server.materializer()); probe.request( 10 ); probe.expectNext( "msg1" ); probe.expectNext( "msg2" ); probe.expectNext( "msg3" ); probe.cancel(); }); } } |
4. Service Stub Test
In this test case, We will be writing a test-case wherein we will be stubbing a service into another service for writing a unit test. Prerequisites 1. In order to to write the unit test for the above scenario, We will stub another service in our service Sample Test File
public class ServiceStubTest { static class WelcomeStub implements WelcomeService { @Override public ServiceCall<NotUsed, String> welcome() { return name -> completedFuture( "Welcome to " + "XYZ International " + "!" ); } } private static ServiceTest.TestServer server; private static ServiceTest.Setup setup = defaultSetup().withCassandra( true ) .withConfigureBuilder(b -> b.overrides (bind(WelcomeService. class ).to(WelcomeStub. class ))); @BeforeClass public static void setUp() { server = startServer(setup); } @AfterClass public static void tearDown() { if (server != null ) { server.stop(); server = null ; } } @Test public void shouldSayWelcome() throws Exception { HelloService helloService = server.client(HelloService. class ); String actualResult = helloService.sayWelcome( "Bob" ).invoke().toCompletableFuture(). get( 20 , SECONDS); String expectedResult = "Welcome to XYZ International !, Bob" ; assertEquals(expectedResult, actualResult);
|
5. Dependency Stub Test-Case(Cassandra Unit Test)
In this scenario we basically have a service which is interacting with Cassandra to persist the data. So, to write the unit -test we will stub the service.
Prerequisite
1.Stub the service for which the test cases are being written.
Sample Test File
public class DependencyWithStubTest { /* Does not need Cassandra A Sample Stub to test a Service Dependancy This need to be bound to Real Service Class as follows Setup setup = defaultSetup() .withConfigureBuilder(b -> b.overrides( bind(UserService.class).to(UserStub.class))); This service does not start Cassandra, and also not use local cassandra. */ static class UserStub implements UserService { @Override public ServiceCall<NotUsed, Object> getUser(String id) { User user = User.builder().id( "1001" ).name( "James" ).age( 27 ).build(); return req -> CompletableFuture.completedFuture(user); } @Override public ServiceCall<NotUsed, Object> deleteUser(String id) { Map<String, String> response = new HashMap<>(); response.put( "success" , "User successfully deleted" ); return req -> CompletableFuture.completedFuture(response); } @Override public ServiceCall<User, Object> newUser() { Map<String, String> response = new HashMap<>(); response.put( "success" , "User successfully created" ); return req -> CompletableFuture.completedFuture(response); } @Override public ServiceCall<User, Object> updateUser(String id) { Map<String, String> response = new HashMap<>(); response.put( "success" , "User successfully updated" ); return request -> CompletableFuture.completedFuture(response); } } private static TestServer server; private static UserService userService; private static Setup setup = defaultSetup().withCassandra( false ) .withConfigureBuilder(b -> b.overrides(bind(UserService. class ).to(UserStub. class ))); @BeforeClass public static void setUp() { server = startServer(setup); userService = server.client(UserService. class ); } @AfterClass public static void tearDown() { if (server != null ) { server.stop(); server = null ; } } @Test public void shouldGetUser() throws Exception { UserService userService = server.client(UserService. class ); Object actualResult = userService.getUser( "1" ).invoke(). toCompletableFuture().get( 20 , SECONDS); User expectedResult = User.builder().id( "1001" ).name( "James" ).age( 27 ).build(); assertEquals(expectedResult, actualResult); } @Test public void shouldDeleteUser() throws Exception { Map<String, String> expectedResult = new HashMap<>(); expectedResult.put( "success" , "User successfully deleted" ); assertEquals(expectedResult, userService.deleteUser( "1" ).invoke(). toCompletableFuture().get( 20 , SECONDS)); } @Test public void shouldUpdateUser() throws Exception { Map<String, String> expectedResult = new HashMap<>(); expectedResult.put( "success" , "User successfully updated" ); User user = User.builder().id( "1001" ).name( "James" ).age( 27 ).build(); assertEquals(expectedResult, userService.updateUser( "1" ).invoke(user). toCompletableFuture().get( 20 , SECONDS)); } @Test public void shouldCreateUser() throws Exception { Map<String, String> expectedResult = new HashMap<>(); expectedResult.put( "success" , "User successfully created" ); User user = User.builder().id( "1001" ).name( "James" ).age( 27 ).build(); assertEquals(expectedResult, userService.newUser().invoke(user). toCompletableFuture().get( 20 , SECONDS)); } } |
6. PersistentEntityWithEmbeddedCassandraTest(Cassandra Integration Test)
In this scenario we are basically testing the service with embedded Cassandra wherein instead of stubbing the service embedded Cassandra will be up and direct calls will be made to embedded Cassandra.
Prerequisite
1. Embedded Cassandra should be true in @BeforeClass i.e withCassandra(true)
public class PersistentEntityWithEmbeddedCassandraTest { private static TestServer server; private static PersistentEntityWithEmbeddedCassandraTest persistentEntityWithEmbeddedCassandraTest; ObjectMapper mapper = new ObjectMapper(); Config conf = ConfigFactory.load( "user.conf" ); ConfigObject user1obj = conf.getObject( "default.bootstrap.user1" ); String user1String = user1obj.render(ConfigRenderOptions.concise()); User bootStrapUser1 = mapper.readValue(user1String, User. class ); ConfigObject user2obj = conf.getObject( "default.bootstrap.user2" ); String user2String = user2obj.render(ConfigRenderOptions.concise()); User bootStrapUser2 = mapper.readValue(user2String, User. class ); ConfigObject user3obj = conf.getObject( "default.create.user" ); String user3String = user3obj.render(ConfigRenderOptions.concise()); User user = mapper.readValue(user3String, User. class ); ConfigObject user4obj = conf.getObject( "default.update.user" ); String user4String = user4obj.render(ConfigRenderOptions.concise()); User updatedUser = mapper.readValue(user4String, User. class ); public PersistentEntityWithEmbeddedCassandraTest() throws IOException { } @BeforeClass public static void setUp() throws Exception { server = startServer(defaultSetup().withCassandra( true )); //Get the DataStax's Cassandra Session Obj CassandraSession cassandraSession = server.injector().instanceOf(CassandraSession. class ); Session session = cassandraSession.underlying().toCompletableFuture().get( 20 , SECONDS); persistentEntityWithEmbeddedCassandraTest = new PersistentEntityWithEmbeddedCassandraTest(); //Create the required schema. createSchema(session); //Add some fake data for testing purpose. populateData(session);
UserService userService = server.client(UserService. class ); Object actualResult = userService.getUser( "1000" ).invoke(). toCompletableFuture().get( 20 , SECONDS); Map<String, String> expectedResult = new HashMap<>(); expectedResult.put( "error" , "User was not found with this id" ); assertEquals(expectedResult, actualResult); } @Test
UserService userService = server.client(UserService. class ); Object actualResult = userService.getUser( "1" ).invoke(). toCompletableFuture().get( 20 , SECONDS); Map<String, String> expectedResult = new LinkedHashMap<>(); expectedResult.put( "id" , "1" ); expectedResult.put( "name" , "Alice" ); expectedResult.put( "age" , "25" ); assertEquals(expectedResult.toString(), actualResult.toString());
UserService userService = server.client(UserService. class ); Map<String, String> expectedResult = new HashMap<>(); expectedResult.put( "success" , "User successfully deleted" ); assertEquals(expectedResult, userService.deleteUser( "2" ).invoke(). toCompletableFuture().get( 20 , SECONDS)); } @Test public void shouldCreateUserInfo() throws Exception { UserService userService = server.client(UserService. class ); Map<String, String> expectedResult = new HashMap<>(); User user = User.builder().id( "1001" ).name( "James" ).age( 27 ).build(); expectedResult.put( "success" , "User successfully created" ); assertEquals(expectedResult, userService.newUser().invoke(user). toCompletableFuture().get( 20 , SECONDS)); } @Test public void shouldUpdateUserInfo() throws Exception { UserService userService = server.client(UserService. class ); Map<String, String> expectedResult = new HashMap<>(); expectedResult.put( "success" , "User successfully updated" ); User updatedUser = User.builder().id( "10" ).name( "Test" ).age( 27 ).build(); assertEquals(expectedResult, userService.updateUser( "10" ).invoke(updatedUser). toCompletableFuture().get( 20 , SECONDS)); } @AfterClass public static void tearDown() throws InterruptedException { if (server != null ) { server.stop(); server = null ; } }
//CREATE user KEYSPACE IF It DOESN'T EXIST session.execute( "CREATE KEYSPACE IF NOT EXISTS user WITH replication = " + "{'class': 'SimpleStrategy', 'replication_factor': '1'}" ); //CREATE users Table If it doesn't exist session.execute( "CREATE TABLE IF NOT EXISTS users(id text primary key," + "name text,age int)" );
private static void populateData(Session session) { session.execute(String.format( "insert into users(id,name,age) values('%s','%s',%d)" , "1" , "Alice" , 25 )); session.execute(String.format( "insert into users(id,name,age) values('%s','%s',%d)" , "2" , "Bob" , 30 )); session.execute( "insert into users(id,name,age) values('10','Foo',30)" );
|
7. Stub Method Test
In this scenario we are basically testing the service which makes call to the method of some other Class within the same Micro-Service, So In this case we will basically mock the method of that class.
Prerequisite
Prerequisite
-
Add jMockit maven dependency
<
dependency
>
<
groupId
>org.jmockit</
groupId
>
<
artifactId
>jmockit</
artifactId
>
<
version
>1.8</
version
>
</
dependency
>
Sample Test File
@Test public void shouldGiveWelcomeMessage() throws Exception { withServer(defaultSetup().withCassandra( true ), server -> { new MockUp() { @SuppressWarnings ( "unused" ) @Mock public String getWelcomeMessage() { return "XYZ International" ; } }; WelcomeService service = server.client(WelcomeService. class ); String actualResult = service.welcome().invoke().toCompletableFuture().get( 20 , SECONDS); String expectedResult = "Welcome to XYZ International!" ; assertEquals(expectedResult, actualResult); }); } |
8. Entity Test
In this scenario we will basically be testing the Persistent Entities using the Actor System
Sample Test File
public class UserEntityTest {
system = ActorSystem.create(); }
JavaTestKit.shutdownActorSystem(system); system = null ; } @Test public void testAddNewUser() { PersistentEntityTestDriver<UserCommand, UserEvent, UserState> driver = new PersistentEntityTestDriver<>(system, new UserEntity(), "1001" ); User user = User.builder().id( "1001" ).name( "James" ).age( 27 ).build(); Outcome<UserEvent, UserState> outcome = driver.run(CreateUser.builder().user(user).build()); assertThat(outcome.events().get( 0 ), is(equalTo(UserCreated.builder().user(user).entityId( "1001" ).build()))); assertThat(outcome.events().size(), is(equalTo( 1 ))); assertThat(outcome.state().getUser().get(), is(equalTo(user))); assertThat(outcome.getReplies().get( 0 ), is(equalTo(Done.getInstance()))); outcome.issues().stream().forEach(System.out::println); assertThat(outcome.issues().isEmpty(), is( true )); } @Test public void testUpdateUser() { PersistentEntityTestDriver<UserCommand, UserEvent, UserState> driver = new PersistentEntityTestDriver<>(system, new UserEntity(), "1001" ); User user = User.builder().id( "1001" ).name( "James" ).age( 27 ).build(); driver.run(CreateUser.builder().user(user).build()); User updateUser = User.builder().id( "1001" ).name( "Testing" ).age( 27 ).build(); Outcome<UserEvent, UserState> outcome = driver.run(UpdateUser.builder().user(updateUser).build()); assertThat(outcome.events().get( 0 ), is(equalTo(UserEvent.UserUpdated.builder().user(updateUser).entityId( "1001" ).build()))); assertThat(outcome.events().size(), is(equalTo( 1 ))); assertThat(outcome.state().getUser().get(), is(equalTo(updateUser))); assertThat(outcome.getReplies().get( 0 ), is(equalTo(Done.getInstance()))); assertThat(outcome.issues().isEmpty(), is( true )); }
public void testDeleteUser() { PersistentEntityTestDriver<UserCommand, UserEvent, UserState> driver = new PersistentEntityTestDriver<>(system, new UserEntity(), "1001" ); User user = User.builder().id( "1001" ).name( "James" ).age( 27 ).build(); driver.run(CreateUser.builder().user(user).build()); Outcome<UserEvent, UserState> outcome = driver.run(DeleteUser.builder().user(user).build()); assertThat(outcome.events().get( 0 ), is(equalTo(UserDeleted.builder().user(user).entityId( "1001" ).build()))); assertThat(outcome.events().size(), is(equalTo( 1 ))); assertThat(outcome.state().getUser(), is(equalTo(Optional.empty()))); assertThat(outcome.getReplies().get( 0 ), is(equalTo(Done.getInstance()))); assertThat(outcome.issues().size(), is(equalTo( 0 ))); }
public void testUserCurrentState() { PersistentEntityTestDriver<UserCommand, UserEvent, UserState> driver = new PersistentEntityTestDriver<>(system, new UserEntity(), "1001" ); User user = User.builder().id( "1001" ).name( "James" ).age( 27 ).build(); Outcome<UserEvent, UserState> outcome1 = driver.run(CreateUser.builder().user(user).build()); assertThat(outcome1.events().get( 0 ), is(equalTo(UserCreated.builder().user(user).entityId( "1001" ).build()))); assertThat(outcome1.state().getUser().get(), is(equalTo(user))); User updateUser = User.builder().id( "1001" ).name( "Testing" ).age( 27 ).build(); Outcome<UserEvent, UserState> outcome2 = driver.run(UpdateUser.builder().user(updateUser).build()); assertThat(outcome2.state().getUser().get(), is(equalTo(updateUser))); Outcome<UserEvent, UserState> outcome3 = driver.run(DeleteUser.builder().user(user).build()); assertThat(outcome3.state().getUser(), is(equalTo(Optional.empty()))); } } |
9. Kafka Publish Test
In this scenario we are basically testing the Kafka producer which is basically putting some data into the Kafka Topic.
Sample Test File
@Test public void shouldEmitGreetingsMessageWhenHelloEntityEmitsEnEvent() { withServer(defaultSetup().withCassandra( true ), server -> { HelloService client = server.client(HelloService. class ); Source<GreetingMessage, ?> source = client.greetingsTopic().subscribe().atMostOnceSource(); // use akka stream testkit TestSubscriber.Probe probe = source.runWith( TestSink.probe(server.system()), server.materializer() ); client.useGreeting( "user" ).invoke( new GreetingMessage( "message" )). toCompletableFuture().get( 10 , SECONDS); FiniteDuration finiteDuration = new FiniteDuration( 20 , SECONDS); GreetingMessage actual = probe.request( 1 ).expectNext(finiteDuration); assertEquals( new GreetingMessage( "message" ), actual); }); } |
10. Kafka Subscriber Test
In this scenario we are basically testing the Kafka Subscriber which is consuming data from one of the Kafka Topic.
Sample Test File
public class BrokerEventConsumerTest { private static final ServiceTest.Setup setup = defaultSetup() .withCassandra( true ) // no need for cluster or cassandra .configureBuilder(b -> b.overrides( // create a fake module, so I'm not starting all the service bind(HelloService. class ).to(HelloStub. class ) // override the clients provided by lagom with my stubs )); @BeforeClass public static void beforeAll() { testServer = ServiceTest.startServer(setup); } @AfterClass public static void afterAll() { if (testServer != null ) { testServer.stop(); testServer = null ; } } private static ServiceTest.TestServer testServer; private static ProducerStub messageProducerStub; @Test public void shouldReceiveMessagesFromUpstream() throws Exception { // (1) GreetingMessage message = new GreetingMessage( "Hi there!" ); HelloService client = testServer.client(HelloService. class ); Done actualResult = client.useGreeting( "1" ).invoke(message).toCompletableFuture().get( 3 , SECONDS); // (4) send a message in the topic messageProducerStub.send(message); Done expectedResult = Done.getInstance(); assertEquals(expectedResult, actualResult); // use a service client instance to interact with the service } // each test would use a ProducerStub or the other or both // ------------------ // --- Stubbing --- // ------------------ // --- my stubby module only starts exactly what I need for the test public static class HelloStub implements HelloService { @Inject public HelloStub(ProducerStubFactory topicFactory) { messageProducerStub = topicFactory.producer(GREETINGS_TOPIC); } @Override public Topic greetingsTopic() { return messageProducerStub.topic(); // this is the only relevant bit of the service stub } @Override public ServiceCall<NotUsed, String> hello(String id) { return name -> completedFuture( "Welcome to Excalibur!" ); } @Override public ServiceCall<NotUsed, String> health() { return request -> CompletableFuture.completedFuture( "Health of HelloWorld service is up..." ); } @Override public ServiceCall<GreetingMessage, Done> useGreeting(String id) { return request -> CompletableFuture.completedFuture(Done.getInstance()); } @Override public ServiceCall<NotUsed, String> sayWelcome(String name) { return request -> CompletableFuture. completedFuture( "Welcome service is being consumed inside Hello service!" ); } } } |
11. Consuming External Service (Integration Test)
In this scenario, We will be writing integration test for the service which is consuming external web service. In this case we will be making direct calls to the external service via Lagom Micro-Service. While the external web service is up and running so that direct calls can be made to check the functionality end to end.
Prerequisite
1.External Web Service should be up and running
Sample Test File
public class ExternalWebServiceTest { private static ServiceTest.TestServer server; @BeforeClass public static void setUp() { server = startServer(defaultSetup().withCassandra( true )); } @AfterClass public static void tearDown() { if (server != null ) { server.stop(); server = null ; } } @Test public void shouldHitExternalAPI() throws Exception { ExternalWebService externalWebService = server.client(ExternalWebService. class ); /*The method posts is making direct calls to external web service*/ ExternalAPIJSON actualResult = externalWebService.posts().invoke(). toCompletableFuture().get( 20 , SECONDS); System.out.println(actualResult); ExternalAPIJSON expectedResult = new ExternalAPIJSON( "two" , "value" ); assertThat(actualResult,instanceOf(ExternalAPIJSON. class )); assertThat(actualResult.key,is(equalTo(expectedResult.key))); assertThat(actualResult.one,is(equalTo(expectedResult.one))); } } |
That was all from testing in Lagom. I hope, you have seen all the major scenarios we have in Lagom framework.
If you find any challenge, Do let me know in the comments.
If you enjoyed this post, I’d be very grateful if you’d help it spread.Keep smiling, Keep coding!
Reblogged this on LearningPool.
As usual,clear and crisp content!