JustLikeThat JustLikeThat - 1 month ago 6
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

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.

Comments