question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support for service integration tests

See original GitHub issue

Support for writing integration tests that involve several interacting services. The idea is that we can launch the services in a similar way as the dev-mode runAll and thereafter run the test that interact with the services using their service clients.

I have explored this and the following code works. The missing pieces is the sbt support for starting the services and tests.

package com.lightbend.lagom.javadsl.testkit

import java.util.function.{ Function => JFunction }

import scala.annotation.varargs
import scala.concurrent.duration._
import scala.concurrent.duration.FiniteDuration
import scala.util.Try

import akka.actor.ActorSystem
import akka.japi.function.Effect
import akka.japi.function.Procedure
import akka.stream.Materializer
import com.google.inject.AbstractModule
import com.lightbend.lagom.internal.cluster.JoinClusterModule
import com.lightbend.lagom.javadsl.api.Service
import com.lightbend.lagom.javadsl.api.ServiceInfo
import com.lightbend.lagom.javadsl.client.ServiceClientGuiceSupport
import com.lightbend.lagom.javadsl.persistence.PersistenceModule
import com.lightbend.lagom.javadsl.pubsub.PubSubModule
import com.lightbend.lagom.javadsl.server.ServiceGuiceSupport
import play.Application
import play.api.Mode
import play.api.Play
import play.api.inject.BindingKey
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.inject.guice.GuiceableModule
import play.inject.Injector
import play.inject.guice.{ GuiceApplicationBuilder => JGuiceApplicationBuilder }

object ServiceIntegrationTest {

  @varargs
  @SafeVarargs
  class TestModule(services: Class[_ <: Service]*) extends AbstractModule with ServiceClientGuiceSupport {
    override def configure(): Unit = {
      services.foreach(bindClient(_))
    }
  }

  @varargs
  @SafeVarargs
  def testModule(services: Class[_ <: Service]*): TestModule =
    new TestModule(services: _*)

  class TestClient(val app: Application) {

    def client[S <: Service](serviceClass: Class[S]): S =
      app.injector().instanceOf(serviceClass)

    def materializer: Materializer = injector.instanceOf(classOf[Materializer])

    def system: ActorSystem = injector.instanceOf(classOf[ActorSystem])

    def injector: Injector = app.injector()

    def stop(): Unit = {
      Try(Play.stop(app.getWrappedApplication))
    }
  }

  def withClient(
    configureBuilder: JFunction[JGuiceApplicationBuilder, JGuiceApplicationBuilder],
    block:            Procedure[TestClient]
  ): Unit = {
    // using Procedure instead of Consumer to support throwing Exception
    val testClient = startClient(configureBuilder)
    try {
      block(testClient)
    } finally {
      testClient.stop()
    }
  }

  def startClient(configureBuilder: JFunction[JGuiceApplicationBuilder, JGuiceApplicationBuilder]): TestClient = {
    val builder =
      new GuiceApplicationBuilder(loadModules = (env, conf) => {
        GuiceableModule.loadModules(env, conf).filterNot(
          _.guiced(env, conf).exists(_.isInstanceOf[ServiceGuiceSupport])
        )
      })
        .in(Mode.Dev)
        .bindings(bind(classOf[ServiceInfo]).to(new ServiceInfo("test")))
        .disable(classOf[PersistenceModule], classOf[PubSubModule], classOf[JoinClusterModule])
        .configure(
          "lagom.service-locator.enabled" -> true,
          "lagom.service-locator.url" -> "http://localhost:8000",
          "akka.actor.provider" -> "akka.actor.LocalActorRefProvider"
        )

    val app = configureBuilder(JGuiceApplicationBuilder.fromScalaBuilder(builder)).build()
    new TestClient(app)
  }

  def eventually(max: FiniteDuration, block: Effect): Unit =
    eventually(max, 100.millis, block)

  def eventually(max: FiniteDuration, interval: FiniteDuration, block: Effect): Unit =
    ServiceTest.eventually(max, interval, block)

  def bind[T](clazz: Class[T]): BindingKey[T] =
    play.inject.Bindings.bind(clazz)

}

and here is example of such a test:

public class ActivityStreamServiceTest {

  @Test
  public void testInvoke() throws Exception {
    withClient(b -> b.bindings(testModule(ActivityStreamService.class, FriendService.class)), testClient -> {
      ActivityStreamService activityStream = testClient.client(ActivityStreamService.class);
      FriendService friend = testClient.client(FriendService.class);

      User usr1 = User.builder().userId("usr1").name("User 1").build();
      friend.createUser().invoke().toCompletableFuture().get(10, TimeUnit.SECONDS);

      CompletionStage<Source<Chirp, ?>> feed = activityStream.getLiveActivityStream().invoke("usr1",
          NotUsed.getInstance());
      Source<Chirp, ?> source = feed.toCompletableFuture().get(3, TimeUnit.SECONDS);
      System.out.println("# got " + source); // FIXME
    });

  }

}

note that the API should be aligned with the API of the ServiceTest

Issue Analytics

  • State:open
  • Created 8 years ago
  • Reactions:19
  • Comments:20 (10 by maintainers)

github_iconTop GitHub Comments

5reactions
jropercommented, May 18, 2017

Four people +1’ing within 10 minutes of each other only counts as one vote.

1reaction
TimPigdencommented, May 18, 2017

For those people who have +'d it recently. I would point out that for me this requirement has largely disappeared. The reason being that I routinely and completely mock every service with an in-memory map or maps to emulate the persistence and simple scala collection processing to emulate the repository actions. I then subject the mock service to all the testing that the full service gets within the sub-project. Finally any cross-service testing can use either an entire set of mocks or the service under test as a service plus the others all as mocks. This works fine so long as your services are effectively only communicating via their path apis. It won’t work though if you’re using Kafka. But if you’re not it’s definitely worth consideration as you can test all sorts of complex things and run tests in a couple of seconds that take far longer when having to instantiate real services.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Integration tests in ASP.NET Core | Microsoft Learn
Integration tests ensure that an app's components function correctly at a level that includes the app's supporting infrastructure, ...
Read more >
6 best practices for integration testing with continuous ...
How integration testing works with CI and DevOps · Run integration tests until something fails · Determine what needs to be added or...
Read more >
Getting Integration Testing Right - Semaphore CI
With the right tools and CI/CD environments, integration testing can become as straightforward as unit testing. Read more.
Read more >
Integration Testing Services - Request for the services
QA Mentor provides advanced software integration testing services for your business. Our experts will provide you with competent assistance.
Read more >
What is Integration Testing (Tutorial ... - Software Testing Help
#1) Integration testing means testing two or more integrated systems in order to ensure that the system works properly. Not only the integration ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found