JustLikeThat JustLikeThat - 10 months ago 53
Scala Question

How to write unit test(s) for custom OutputStream

I've had to create an

OutputStream
class (inspired by this) that writes to a logger instead of
stdout
, so I came up with the following:

case class OutputStreamLogger(level: Level) extends OutputStream {
val logger: Logger = org.slf4j.LoggerFactory.getLogger("OutputStreamLogger")
private var mem = ""

/**
* Writes byte to Logger, flushes automatically at EOL
*
* @param b Int Int representation of character to get written
*
* @return Unit
*/
def write(b: Int): Unit = {
// To prevent internal conversion from byte to int
val bytes = new Array[Byte](1)
// Get least significant byte from int argument
bytes(0) = (b & 0xff).toByte
// Turn byte array into String
mem += new String(bytes)

// Automatically flush at EOL
if(mem.endsWith("\n")) {
// Grab everything but newline
mem = mem.substring(0, mem.length - 1)
// Log it
flush()
}
}

/**
* Sends output bytes to logger at specified level
*
* @return Unit
*/
override def flush(): Unit = {
level match {
// Passing the format then the String eliminates
// need to check if logging at that level is enabled
case Level.TRACE => logger.trace("{}", mem)
case Level.DEBUG => logger.debug("{}", mem)
case Level.INFO => logger.info("{}", mem)
case Level.WARN => logger.warn("{}", mem)
case Level.ERROR => logger.error("{}", mem)
}
// Clear out buffer
mem = ""
}
}


How would I unit test this? I've looked at other examples for unit testing
OutputStream
s, but they all use
PrintStream
where I would want to use my
OutputStreamLogger
and don't seem to be testing the
write
and
flush
methods

Answer Source

The main testability issue with your output stream is probably this declaration:

val  logger:  Logger  =  org.slf4j.LoggerFactory.getLogger("OutputStreamLogger")

You have nailed the logger down to a concrete instance, where the initialization of that instance can not be customized. You can either now use a slf4j logging configuration during unit testing that writes to a temporary file and read that back. Maybe there is also a custom appender available that writes to an in memory buffer.

The other possibility is to change that declaration and allow a logger specification upon instance creation. You can then use this to inject a mock logger.

Testing then would just be writing something to the output stream and see if a newline flushes the output to the logger.

BTW - your implementation looks very inefficient as it creates a new string for every byte written. And having a local variable and a member with the same name is also confusing.