Testing in Lagom(Advanced Testing Scenarios)


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

  1. Basic Junit Test

Writing a basic Junit test-case is very easy.

Prerequisites

  1. Add Junit maven dependency
 junit
 junit
 4.11
 test
@Test
publicvoidtestSum() {
inta = 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

Lagom Service Unit Test
publicclassWelcomeServiceTest {
privatestaticServiceTest.TestServer server;

@BeforeClass

publicstaticvoidsetUp() {

server = startServer(defaultSetup().withCassandra(false));

}

@AfterClass

publicstaticvoidtearDown() {

        if(server != null) {
            server.stop();
            server = null;
        }
    }
@Test
    publicvoidshouldWelcome() throwsException {
        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

 

Stream Test-Case
publicclassEchoServiceTest {
@Test
    publicvoidshouldEchoStream() throwsException {
        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

Service Stub Test
publicclassServiceStubTest {
    staticclassWelcomeStub implementsWelcomeService {
        @Override
        publicServiceCall<NotUsed, String> welcome() {
            returnname -> completedFuture("Welcome to "+ "XYZ International "+ "!");
        }
    }
    privatestaticServiceTest.TestServer server;
    privatestaticServiceTest.Setup setup = defaultSetup().withCassandra(true)
            .withConfigureBuilder(b -> b.overrides
                    (bind(WelcomeService.class).to(WelcomeStub.class)));
    @BeforeClass
    publicstaticvoidsetUp() {
        server = startServer(setup);
    }
@AfterClass
    publicstaticvoidtearDown() {
        if(server != null) {
            server.stop();
            server = null;
        }
    }
@Test
    publicvoidshouldSayWelcome() throwsException {
        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

publicclassDependencyWithStubTest {
    /*
    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.
 */
    staticclassUserStub implementsUserService {
        @Override
        publicServiceCall<NotUsed, Object> getUser(String id) {
            User user = User.builder().id("1001").name("James").age(27).build();
            returnreq -> CompletableFuture.completedFuture(user);
        }
        @Override
        publicServiceCall<NotUsed, Object> deleteUser(String id) {
            Map<String, String> response = newHashMap<>();
            response.put("success", "User successfully deleted");
            returnreq -> CompletableFuture.completedFuture(response);
        }
        @Override
        publicServiceCall<User, Object> newUser() {
            Map<String, String> response = newHashMap<>();
            response.put("success", "User successfully created");
            returnreq -> CompletableFuture.completedFuture(response);
        }
        @Override
        publicServiceCall<User, Object> updateUser(String id) {
            Map<String, String> response = newHashMap<>();
            response.put("success", "User successfully updated");
            returnrequest -> CompletableFuture.completedFuture(response);
        }
    }
    privatestaticTestServer server;
    privatestaticUserService userService;
    privatestaticSetup setup = defaultSetup().withCassandra(false)
            .withConfigureBuilder(b -> b.overrides(bind(UserService.class).to(UserStub.class)));
    @BeforeClass
    publicstaticvoidsetUp() {
        server = startServer(setup);
         userService = server.client(UserService.class);
    }
    @AfterClass
    publicstaticvoidtearDown() {
        if(server != null) {
            server.stop();
            server = null;
        }
    }
    @Test
    publicvoidshouldGetUser() throwsException {
        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
    publicvoidshouldDeleteUser() throwsException {
        Map<String, String> expectedResult = newHashMap<>();
        expectedResult.put("success", "User successfully deleted");
        assertEquals(expectedResult, userService.deleteUser("1").invoke().
                toCompletableFuture().get(20, SECONDS));
    }
    @Test
    publicvoidshouldUpdateUser() throwsException {
        Map<String, String> expectedResult = newHashMap<>();
        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
    publicvoidshouldCreateUser() throwsException {
        Map<String, String> expectedResult = newHashMap<>();
        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)

publicclassPersistentEntityWithEmbeddedCassandraTest {
    privatestaticTestServer server;
    privatestaticPersistentEntityWithEmbeddedCassandraTest
            persistentEntityWithEmbeddedCassandraTest;
    ObjectMapper mapper = newObjectMapper();
    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);
    publicPersistentEntityWithEmbeddedCassandraTest() throwsIOException {
    }
 @BeforeClass
    publicstaticvoidsetUp() throwsException {
        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 = newPersistentEntityWithEmbeddedCassandraTest();
        //Create the required schema.
        createSchema(session);
        //Add some fake data for testing purpose.
        populateData(session);

}

@Test

publicvoidshouldNotReturnUserInfo() throwsException {

        UserService userService = server.client(UserService.class);
        Object actualResult = userService.getUser("1000").invoke().
                toCompletableFuture().get(20, SECONDS);
        Map<String, String> expectedResult = newHashMap<>();
        expectedResult.put("error", "User was not found with this id");
        assertEquals(expectedResult, actualResult);
    }
@Test

publicvoidshouldReturnUserInfo() throwsException {

        UserService userService = server.client(UserService.class);
        Object actualResult = userService.getUser("1").invoke().
                toCompletableFuture().get(20, SECONDS);
        Map<String, String> expectedResult = newLinkedHashMap<>();
        expectedResult.put("id", "1");
        expectedResult.put("name", "Alice");
        expectedResult.put("age", "25");
        assertEquals(expectedResult.toString(), actualResult.toString());

}

@Test

publicvoidshouldDeleteUserInfo() throwsException {

        UserService userService = server.client(UserService.class);
        Map<String, String> expectedResult = newHashMap<>();
        expectedResult.put("success", "User successfully deleted");
        assertEquals(expectedResult, userService.deleteUser("2").invoke().
                toCompletableFuture().get(20, SECONDS));
    }
@Test
    publicvoidshouldCreateUserInfo() throwsException {
        UserService userService = server.client(UserService.class);
        Map<String, String> expectedResult = newHashMap<>();
        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
    publicvoidshouldUpdateUserInfo() throwsException {
        UserService userService = server.client(UserService.class);
        Map<String, String> expectedResult = newHashMap<>();
        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
    publicstaticvoidtearDown() throwsInterruptedException {
        if(server != null) {
            server.stop();
            server = null;
        }
    }

privatestaticvoidcreateSchema(Session session) {

        //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)");

}

privatestaticvoidpopulateData(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

  1. Add jMockit maven dependency

    <dependency>
        <groupId>org.jmockit</groupId>
        <artifactId>jmockit</artifactId>
        <version>1.8</version>
    </dependency>

Sample Test File

@Test
publicvoidshouldGiveWelcomeMessage() throwsException {
    withServer(defaultSetup().withCassandra(true), server -> {
        newMockUp() {
            @SuppressWarnings("unused")
            @Mock
            publicString 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

publicclassUserEntityTest { 

privatestaticActorSystem system;

@BeforeClass

publicstaticvoidsetup() {

        system = ActorSystem.create();
    }

@AfterClass

publicstaticvoidteardown() {

        JavaTestKit.shutdownActorSystem(system);
        system = null;
    }
    @Test
    publicvoidtestAddNewUser() {
        PersistentEntityTestDriver<UserCommand, UserEvent, UserState> driver =
                newPersistentEntityTestDriver<>(system, newUserEntity(), "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
    publicvoidtestUpdateUser() {
        PersistentEntityTestDriver<UserCommand, UserEvent, UserState> driver =
                newPersistentEntityTestDriver<>(system, newUserEntity(), "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));
    }

@Test

    publicvoidtestDeleteUser() {
        PersistentEntityTestDriver<UserCommand, UserEvent, UserState> driver =
                newPersistentEntityTestDriver<>(system, newUserEntity(), "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)));
    }

@Test

    publicvoidtestUserCurrentState() {
        PersistentEntityTestDriver<UserCommand, UserEvent, UserState> driver =
                newPersistentEntityTestDriver<>(system, newUserEntity(), "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
publicvoidshouldEmitGreetingsMessageWhenHelloEntityEmitsEnEvent() {
    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(newGreetingMessage("message")).
                toCompletableFuture().get(10, SECONDS);
        FiniteDuration finiteDuration = newFiniteDuration(20, SECONDS);
        GreetingMessage actual = probe.request(1).expectNext(finiteDuration);
        assertEquals(newGreetingMessage("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

publicclassBrokerEventConsumerTest {
    privatestaticfinalServiceTest.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
    publicstaticvoidbeforeAll() {
        testServer = ServiceTest.startServer(setup);
    }
    @AfterClass
    publicstaticvoidafterAll() {
        if(testServer != null) {
            testServer.stop();
            testServer = null;
        }
    }
    privatestaticServiceTest.TestServer testServer;
    privatestaticProducerStub messageProducerStub;
    @Test
    publicvoidshouldReceiveMessagesFromUpstream() throwsException {
        // (1)
        GreetingMessage message = newGreetingMessage("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
    publicstaticclassHelloStub implementsHelloService {
        @Inject
        publicHelloStub(ProducerStubFactory topicFactory) {
            messageProducerStub = topicFactory.producer(GREETINGS_TOPIC);
        }
        @Override
        publicTopic greetingsTopic() {
            returnmessageProducerStub.topic();  // this is the only relevant bit of the service stub
        }
        @Override
        publicServiceCall<NotUsed, String> hello(String id) {
            returnname -> completedFuture("Welcome to Excalibur!");
        }
        @Override
        publicServiceCall<NotUsed, String> health() {
            returnrequest -> CompletableFuture.completedFuture("Health of HelloWorld service is up...");
        }
        @Override
        publicServiceCall<GreetingMessage, Done> useGreeting(String id) {
            returnrequest -> CompletableFuture.completedFuture(Done.getInstance());
        }
        @Override
        publicServiceCall<NotUsed, String> sayWelcome(String name) {
            returnrequest -> 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 

publicclassExternalWebServiceTest {
    privatestaticServiceTest.TestServer server;
    @BeforeClass
    publicstaticvoidsetUp() {
        server = startServer(defaultSetup().withCassandra(true));
    }
    @AfterClass
    publicstaticvoidtearDown() {
        if(server != null) {
            server.stop();
            server = null;
        }
    }
    @Test
    publicvoidshouldHitExternalAPI() throwsException {
        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 = newExternalAPIJSON("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!

Advertisements

About deepak028

There is nothing much to describe me.However, I am a very ordinary person who believes in sharing knowledge.
This entry was posted in Scala. Bookmark the permalink.

2 Responses to Testing in Lagom(Advanced Testing Scenarios)

  1. As usual,clear and crisp content!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s