Support for service integration tests
See original GitHub issueSupport 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:
- Created 8 years ago
- Reactions:19
- Comments:20 (10 by maintainers)
Top 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 >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Four people +1’ing within 10 minutes of each other only counts as one vote.
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.