scalaakkastdoutscalatesttestkit

How to test the behaviour of an akka actor, if it reads from `stdin` and writes to `stdout`?


I'm writing a UCI interpreter, as an Akka Finite State Machine. As per the specification, the interpreter must write its output to stdout, and take its input from stdin.

I have a test suit for the actor, and I can test some aspects (message related) of its behaviour, but I don't know how to capture the stdout to make assertions, nor how to send it its input through stdin. I've explored the scalatest API to the best of my abilities, but can't find how to achieve what I need.

This is the current test class:

package org.chess

import akka.actor.ActorSystem
import akka.testkit.{TestKit, TestProbe}
import org.chess.Keyword.Quit
import org.scalatest.wordspec.AnyWordSpecLike
import org.scalatest.{BeforeAndAfterAll, Matchers}

import scala.concurrent.duration._
import scala.language.postfixOps

class UCIInterpreterSpec(_system: ActorSystem)
  extends TestKit(_system)
    with Matchers
    with AnyWordSpecLike
    with BeforeAndAfterAll {

  def this() = this(ActorSystem("UCIInterpreterSpec"))

  override def afterAll: Unit = {
    super.afterAll()
    shutdown(system)
  }

  "A UCI interpreter" should {

    "be able to quit" in {
      val testProbe = TestProbe()
      val interpreter = system.actorOf(UCIInterpreter.props)
      testProbe watch interpreter
      interpreter ! Command(Quit, Nil)
      testProbe.expectTerminated(interpreter, 3 seconds)
    }

  }

}

Of course, knowing that the interpreter can quit is useful... but not very useful. I need to test, for example, if sending the string isready to the interpreter, it returns readyok.

Is it possible that I am overcomplicating the test by using akka.testkit, instead of a simpler framework? I would like to keep using a single testing framework for simplicity, and I will need to test many other actor-related elements of the system, so if this could be solved without leaving the akka-testkit/scalatest domain, it would be fantastic.

Any help will be appreciated. Thanks in advance.


Solution

  • You need to change the design of your Actor.

    The Actor should not read stdin or write stdout directly. Instead, give the actor objects in the Props that provide input and accept output. stdin could be something like () => String that is called each time input is needed. stdout could be String => Unit that is called each time output is generated. Or you could use Streams or similar constructs that are designed to be abstract sources and sinks of data.

    In production code you pass objects that use stdin and stdout, but for test code you pass objects that read and write memory buffers. You can then check that the appropriate input is consumed by the Actor and that the appropriate output is generated by the Actor.