Testing

Dependencies

To use the CSW Testkit, you must add the following dependency in your project:

sbt
libraryDependencies += "com.github.tmtsoftware.csw" %% "csw-testkit" % "5.0.1"

Introduction

CSW comes with a dedicated csw-testkit module for supporting tests. This module includes following multiple individual testkits:

  • LocationTestKit : starts and stops the Location Server
  • ConfigTestKit : starts and stops the Config Server
  • EventTestKit : starts and stops the Event Service (Note : This uses embedded-redis to start redis sentinel and master)
  • AlarmTestKit : starts and stops the Alarm Service (Note : This uses embedded-redis to start redis sentinel and master)
  • DatabaseTestKit : starts and stops the Database Service (Note : This uses embedded postgres)
  • FrameworkTestKit : in most of the cases, you will end up using this testkit. FrameworkTestKit is created by composing all the above mentioned testkits. Hence, it supports starting and stopping all provided CSW services.
Note

All the testkits require the Location Server to be up and running. Hence, the first thing all testkits do is to start a Location Server. You do not need to start it explicitly.

TestKits

When you really want granular level access to testkits, then only you would want to use LocationTestKit|ConfigTestKit|EventTestKit|AlarmTestKit|DatabaseTestKit|FrameworkTestKit directly. You can create instance of FrameworkTestKit as shown below:

Scala
source// create instance of framework testkit
private val frameworkTestKit = FrameworkTestKit()

// starts Config Server and Event Service
override protected def beforeAll(): Unit = frameworkTestKit.start(ConfigServer, EventServer)

// stops all services started by this testkit
override protected def afterAll(): Unit = frameworkTestKit.shutdown()
Java
sourceprivate static final FrameworkTestKit frameworkTestKit = FrameworkTestKit.create();

@BeforeClass
public static void beforeAll() {
    frameworkTestKit.start(JCSWService.ConfigServer, JCSWService.EventServer);
}

@AfterClass
public static void afterAll() {
    frameworkTestKit.shutdown();
}
Note

Similarly, you can use other testkits. Please refer to API docs for more details.

Enabling Logging

You can enable logging service using LoggingSystemFactory.forTestingOnly() factory. This is useful for debugging purpose. It logs messages to console.

import csw.logging.client.scaladsl.LoggingSystemFactory

// make sure you have implicit actor system in scope or provide it explicitly
LoggingSystemFactory.forTestingOnly()

Spawning components

FrameworkTestKit provides an easy way to spawn components in Container or Standalone mode. Use the spawnContainer method provided by FrameworkTestKit to start components in container mode andspawnStandalone method to start a component in standalone mode.

The example below shows how to spawn container or component in standalone mode using the Framework testkit.

Scala
source
// starting container from container config using testkit frameworkTestKit.spawnContainer(ConfigFactory.load("SampleContainer.conf")) // starting standalone component from config using testkit // val componentRef: ActorRef[ComponentMessage] = // frameworkTestKit.spawnStandaloneComponent(ConfigFactory.load("SampleHcdStandalone.conf"))
Java
source
// starting container from container config using testkit frameworkTestKit.spawnContainer(ConfigFactory.load("JSampleContainer.conf")); // starting standalone component from config using testkit // ActorRef<ComponentMessage> componentRef = // frameworkTestKit.spawnStandaloneComponent(ConfigFactory.load("SampleHcdStandalone.conf"));

Other ways to spawn Assembly and HCD in the standalone mode using Framework testkit.

Spawning a HCD

Scala
sourceframeworkTestKit.spawnHCD(Prefix("TCS.sampleHcd"), (ctx, cswCtx) => new SampleHcdHandlers(ctx, cswCtx))

Spawning an Assembly

Scala
sourceframeworkTestKit.spawnAssembly(Prefix("TCS.sampleAssembly"), (ctx, cswCtx) => new SampleHandlers(ctx, cswCtx))

Full source at GitHub

Test Framework Integration

ScalaTest

If you are using ScalaTest, then you can extend csw.testkit.scaladsl.ScalaTestFrameworkTestKit to have the Framework testkit automatically start the provided services before running tests and shut them down when the tests are complete. This is done in beforeAll and afterAll from the BeforeAndAfterAll trait. If you override that method you should call super.beforeAll to start services and super.afterAll to shutdown the test kit.

JUnit

If you are using JUnit then you can use csw.testkit.javadsl.FrameworkTestKitJunitResource to have the framework test kit automatically start the provided services before running tests and shut them down when the tests are complete.

Supported CSW Services by FrameworkTestKit

ScalaTestFrameworkTestKit and FrameworkTestKitJunitResource both support starting one or more of the following services.

Scala Java
CSWService.LocationServer JCSWService.LocationServer
CSWService.ConfigServer JCSWService.ConfigServer
CSWService.EventServer JCSWService.EventServer
CSWService.AlarmServer JCSWService.AlarmServer
CSWService.DatabaseServer JCSWService.DatabaseServer

The example below shows the usage of ScalaTestFrameworkTestKit and FrameworkTestKitJunitResource and how you can start the above mentioned services as per your need.

Scala
sourceimport com.typesafe.config.ConfigFactory
import csw.testkit.scaladsl.CSWService.{AlarmServer, EventServer}
import csw.testkit.scaladsl.ScalaTestFrameworkTestKit
import org.scalatest.funsuite.AnyFunSuiteLike

class ScalaTestIntegrationExample extends ScalaTestFrameworkTestKit(AlarmServer, EventServer) with AnyFunSuiteLike {

  test("test spawning component in standalone mode") {
    spawnStandalone(ConfigFactory.load("SampleHcdStandalone.conf"))

    // .. assertions etc.

  }
}
Java
source
import com.typesafe.config.ConfigFactory; import csw.testkit.javadsl.FrameworkTestKitJunitResource; import csw.testkit.javadsl.JCSWService; import org.junit.ClassRule; import org.junit.Test; import java.util.Arrays; public class JUnitIntegrationExample { @ClassRule public static final FrameworkTestKitJunitResource testKit = new FrameworkTestKitJunitResource(Arrays.asList(JCSWService.AlarmServer, JCSWService.EventServer)); @Test public void testSpawningComponentInStandaloneMode() { testKit.spawnStandalone(ConfigFactory.load("JSampleHcdStandalone.conf")); // ... assertions etc. } }
Note

You do not need to externally start any services like the Event Service, Config Service, Location Service etc. via csw-services application. Testkits will start required services as a part of initialization. For the Event and Alarm service, it uses an instance of embedded-redis.

Initializing alarms & getting severity of alarms

To use alarms in your tests, the list of available alarms must be defined prior to starting the Alarm Service. This will create alarms in the Alarm Store, and then the alarm severities can be set individually.
See the Alarm Service Documentation for more information on the alarm configuration file.

The AlarmTestKit provides methods to initialize the alarm store, as well as get the current severity of an alarm for testing purposes.

The example below shows the usage initAlarms & getCurrentSeverity methods using AlarmTestKit.

Scala
sourceclass ScalaAlarmTestKitExample extends ScalaTestFrameworkTestKit(AlarmServer) with AnyFunSuiteLike {
  import frameworkTestKit.alarmTestKit._
  test("test initializing alarms via config") {
    initAlarms(ConfigFactory.parseResources("valid-alarms.conf"))

    // .. assertions etc.
  }

  test("use getCurrentSeverity to fetch severity of initialized alarms") {
    val severity = getCurrentSeverity(AlarmKey(Prefix(NFIRAOS, "trombone"), "tromboneAxisLowLimitAlarm"))
    // .. assertions etc.
  }
}
Java
sourceclass JUnitAlarmTestKitExample {

    @ClassRule
    public static final FrameworkTestKitJunitResource testKit =
            new FrameworkTestKitJunitResource(Arrays.asList(JCSWService.AlarmServer));
    private AlarmTestKit alarmTestKit = testKit.frameworkTestKit().alarmTestKit();

    @Test
    public void testInitAlarms() {
        alarmTestKit.initAlarms(ConfigFactory.load("valid-alarms.conf"), true);
        // ... assertions etc.
    }
    @Test
    public void useGetCurrentSeverityToFetchSeverityOfInitializedAlarms() {
        Key.AlarmKey key = new Key.AlarmKey(new Prefix(JSubsystem.NFIRAOS, "trombone"),"tromboneAxisLowLimitAlarm");
        FullAlarmSeverity alarmSeverity = alarmTestKit.getCurrentSeverity(key);
        // ... assertions etc.
    }
}

Using dslContext provided by DatabaseTestKit

DatabaseTestKit starts embedded postgres with default database, user and password. You can override these defaults by adding custom configurations. For more details refer Database Service Documentation

The DatabaseTestKit also provides methods to create database client and execute queries.

The example below shows the usage dslContext & databaseServiceFactory methods using DatabaseTestKit.

Scala
sourceclass ScalaDatabaseTestKitExample extends ScalaTestFrameworkTestKit(DatabaseServer) with AnyFunSuiteLike {
  import frameworkTestKit.databaseTestKit.*

  test("test using dsl context") {
    val queryDsl = dslContext()
    // .. queries, assertions etc.
  }

  test("test using database service factory") {
    // Usage of Await.result is fine in test scope here, as `databaseServiceFactory().makeDsl()` returns Future.
    val queryDsl = Await.result(databaseServiceFactory().makeDsl(frameworkTestKit.locationService, "postgres"), 2.seconds)
    // .. queries, assertions etc.
  }
}
Java
sourceclass JUnitDatabaseTestKitExample {

    @ClassRule
    public static final FrameworkTestKitJunitResource testKit =
            new FrameworkTestKitJunitResource(Arrays.asList(JCSWService.DatabaseServer));
    private DatabaseTestKit databaseTestKit = testKit.frameworkTestKit().databaseTestKit();

    @Test
    public void testUsingDslContext() {
        DSLContext queryDsl = databaseTestKit.dslContext("postgres");
        // ... queries, assertions etc.
    }

    @Test
    public void testUsingDatabaseServiceFactory() throws ExecutionException, InterruptedException, TimeoutException {
        DSLContext queryDsl = databaseTestKit.databaseServiceFactory("postgres", "postgres").jMakeDsl(testKit.jLocationService(), "postgres").get(5, TimeUnit.SECONDS);
        // ... queries, assertions etc.
    }
}

Unit Tests

The goal of unit testing is to break your application into the smallest testable units and test them individually, isolating a specific piece of functionality and ensuring it is working correctly. It is always a good idea to write more unit test cases and relatively fewer component and integration tests. If you want to get an idea of how many tests you should have in different types of testing phases (Unit/Component/Integration), refer to this blog

Unit testing simple Scala/Java classes or objects is straightforward. You can mock external dependencies using Mockito. Refer to the Mockito section for more details.

The following links provide guides for testing applications using different modules of Akka:

Multi-JVM Tests

Testing asynchronous distributed systems requires special tooling/framework support. Sbt has a plugin called sbt-multi-jvm which helps to test systems across multiple JVMs or machines. This is especially useful for integration testing where multiple systems communicate with each other.

You can find more details on multi-JVM tests here.

You can also refer to some examples in CSW for writing your own multi-JVM tests. For example: CommandServiceTest.scala

In case you want to run your multi-JVM tests across machines, refer to this multi-node testing guide here.

Mockito

Mocks are used so that unit tests can be written independent of dependencies.
CSW uses Mockito for writing unit tests. ScalaTest comes with the MockitoSugar trait which provides some basic syntax sugar for Mockito.

For example: ContainerBehaviorTest.scala