Code is in Scala, but the question is relevant to other lanugages also.
Let say we have some class:
class SomeClass(inputString: String) {
private val name: String = readName()
private val method: Method = readMethod()
def sayHello(): String = {
if (method == USE_XXX) {
s"Hello, my name is $name"
} else "I don't know"
}
private def readName(): String = {
val nameRegex ="""(?mi)^NAME\s*:\s*(.*)$""".r
val name = for (m <- nameRegex.findFirstMatchIn(inputString)) yield m.group(1)
name.getOrElse(throw new CustomException("No name in input"))
}
private def readMethod(): Method = {
val methodRegex = """(?mi)^METHOD\s*:\s*(.*)$""".r
val method = for (m <- methodRegex.findFirstMatchIn(inputString)) yield m.group(1)
method.getOrElse("") match {
case "XXX" => USE_XXX
case _ => UNKNOWN
}
}
sealed trait Method
case object USE_XXX extends Method
case object UNKOWN extends Method
}
As you can see it's public interface is only the method sayHello()
.
However, when the object of that class is instantiated readName can throw an exception as the name
field is required.
The question is, how to unit test this class. I would like to write unit tests that will test both private functions against input specific for the given function (isolation), e.g.:
tests for readName() with texts:
1) "NAME: ok"
2) "NAME: first occurance\nNAME: second occurance"
3) "name: keyword lowercase"
4) "NAME : whitespaces around semicolon"
5) AssertThrows[CustomException](SomeClass("NONAME"))
test for readMethod() with texts:
1) "NO METHOD KEYWORD" -> should have be UNKOWN
2) "METHOD: XXX"
3) "method: xxx"
etc.
The problem is that input texts for testing readMethod() should also contain the NAME: keyword to prevent throwing an exception. If there are also another fields and validation rules that in each test it must be used input with fulfills all validation rules but the one under the test and this makes test suit hard to maintain.
How to deal with such a problem?
The easiest way is to move the exception throw to sayHello()
method but this has a drawback - it may be called after some expensive calculations are performed in the other part of the system, while it should warn the user as early as possible ("Hello dear user, data you provided is wrong, the name is missing. Do you really want to continue?"- or something similar).