HomeAbout UsNews, company reports and blogBlogTechBusiness-Friendly Functional Programming – Part 2: Testability

Business-Friendly Functional Programming – Part 2: Testability

Article by Yang Bo
Published on Read in 10 mins

In part one of this blog series, we introduced a monadic macro from the Each library, and showed how it can simplify asynchronous operations. In part two, we will investigate how to gain testability without mocks and stubs, and still keep transparency from the business point of view.

Two years ago, Ken Scambler wrote a blog To Kill a Mockingtest, which describes why we should use the command pattern to wipe out side effects and enhance testability. In this article, we will see how to implement the command pattern with the help of Scalaz and the Each library.

The problem of testability

In the last code example of part one, we created an asyncContactsXhtml method with Each; however, we didn't create unit tests for the method.

We may want to create some unit tests for asyncContactsXhtml like this:

[code lang=”scala”]
val timeout = 3.seconds
val contactsXhtml = Await.result(asyncContactsXhtml, timeout);

assert(contactsXhtml ==The expected content here …)
[/code]

Unfortunately this way does not work.

asyncContactsXhtml depends on the external static functions asyncGetEmailList and asyncGetContactNameByEmail.

The result of asyncContactsXhtml varies with the different results that asyncGetEmailList and asyncGetContactNameByEmail might return.

In order to make the test result stable, we have to provide stable results of asyncGetEmailList and asyncGetContactNameByEmail in the testing environment.

However, no testing frameworks in the Scala world are capable of mocking the results of static functions.

In our real system, a microservice usually depends on some side effects such as other services or internal states, so we have to employ integration testing. Even here though, the tests are still very delicate and may fail due to non-deterministic time-critical condition or IO problems.

Command pattern using Free and Monad

In order to achieve both asynchrony and testability, we introduced Scalaz's Free. We split the application into a core layer and a shell layer, and defined the boundary between the two layers via the command pattern. This approach was described as Functional Core, Imperative Shell by Gary Bernhardt.

ContactCommand

We defined a trait ContactCommand as the common supertype for commands of this application, and provided case class/case object implementations for each kind of side effect.

[code lang=”scala”]
sealed trait ContactCommand[A]

object ContactCommand {
final case object GetEmailList extends ContactCommand[List[String]]
final case class GetContactNameByEmail(email: String) extends ContactCommand[String]
}
[/code]

A ContactCommand instance represents a side-effecting instruction that will be passed from the core layer to the shell layer. The ContactCommand instance itself does not contain any information about its behavior.

Script

Instead of putting business logic in a controller method, we added a method that creates a Script, representing a lazily evaluated procedure.

Script is an alias of scalaz.Free, which is a container that consists of multiple ContactCommands.

[code lang=”scala”]
/**
* A container of [[ContactCommand]] that represent a chunk of control flow within these [[ContactCommand]].
*/
type Script[A] = Free[ContactCommand, A]

[/code]

Scripts can be created by lifting a ContactCommand or composing other sub-Scripts.

[code lang=”scala”]
/**
* Creates a [[Script]] by lifting an [[ContactCommand]]
*/
def toScript[A](command: ContactCommand[A]): Script[A] = {
Free.liftF(command)
}

/**
* Returns the script of /contacts business logic, which consists of other scripts.
*/
def contactsScript: Script[xml.Elem] = {
toScript(GetEmailList).flatMap { emailList =>
emailList.traverseM[Script, xml.Elem] { email =>
if (email.matches("""[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}""")) {
toScript(GetContactNameByEmail(email)).map { name =>
List(<tr>
<td>
{name}
</td>
<td>
{email}
</td>
</tr>)
}
} else {
(Nil: List[xml.Elem]).point[Script]
}
}.map { trs =>
<html>
<body>
<table>
{trs}
</table>
</body>
</html>
}
}
}
[/code]

Asynchronous interpreter

Once the Script has been created, the controller can create an interpreter to evaluate the Script.

[code lang=”scala”]
def externalServiceUrlPrefix = "http://api-host-name/contact/"

/**
* Fetches list of email of all contacts from a remote service.
*/
private def asyncGetEmailList(): Future[List[String]] = {
wsClient.url(externalServiceUrlPrefix).get().map { response =>
val JsArray(jsonSeq) = response.json
(for {
JsString(element) <- jsonSeq
} yield element) (collection.breakOut(List.canBuildFrom))
}
}

/**
* Query the contact full name that corresponds to an email.
*/
private def asyncGetContactNameByEmail(email: String): Future[String] = {
val url = raw"""$externalServiceUrlPrefix${UriEncoding.encodePathSegment(email, "UTF-8")}"""
wsClient.url(url).get().map { response =>
response.body
}
}

/**
* The interpreter that maps an [[ContactCommand]] to a [[Future]]
*/
def interpreter = new (ContactCommand ~> Future) {
override def apply[A](fa: ContactCommand[A]): Future[A] = {
fa match {
case GetEmailList =>
asyncGetEmailList()
case GetContactNameByEmail(email) =>
asyncGetContactNameByEmail(email)
}
}
}

/**
* HTTP handler for /contacts
*/
def contacts = Action.async {
ContactScript.contactsScript.foldMap(interpreter).map(Ok(_))
}

[/code]

The interpreter will dispatch different ContactCommands to different underlying services. For example, when a GetEmailList is passed to the interpreter, it returns a Future that will asynchronously fetch the email list.

Asynchronous methods such as asyncGetEmailList and asyncGetContactNameByEmail perform HTTP request to these services, and return an atomic Future for each request. Free.foldMap composes these atomic Futures into a complex Future that represents the business logic of the entire process.

Thus, the entire process is as asynchronous as the example in part one.

Testing

Up to now, we have separated the application into two layers, and defined the protocol between the two layers:

  1. The command case classes, which define the protocol between the core layer and shell layer.
  2. The core layer, which is the contactsScript method, containing all the business logic and no side effects.
  3. The shell layer, which is the rest of the code, containing all the side effects and no business logic.

In the real world, the core layer will contain many more lines of code than the shell layer, because it contains all the business logic, which will grow continuously. So we still need to find an efficient way to test the core layer.

Fortunately we can create unit tests for the core layer very easily.

A test case looks like a complete shell layer, except it provides mocked data of external services, instead of visiting real external services.

[code lang=”scala”]
@org.junit.Test
def testContactScript(): Unit = {
val interpreter = new (ContactCommand ~> Id) {
private val data = Map(
"atryyang@thoughtworks.com" -> "Yang Bo",
"invalid-mail-address" -> "N/A",
"john.smith@gmail.com" -> "John Smith"
)
override def apply[A](fa: ContactCommand[A]): A = {
fa match {
case GetEmailList =>
data.keys.toList
case GetContactNameByEmail(email) =>
data(email)
}
}
}

val xhtml = ContactScript.contactsScript.foldMap(interpreter)

Assert.assertEquals("html", xhtml.label)
val rows = (xhtml "body" "table" "tr")
Assert.assertEquals(2, rows.length)

// Text in first cell in first row should be "Yang Bo"
Assert.assertEquals("Yang Bo", (rows(0) "td")(0).text.trim)

// Text in second cell in first row should be "atryyang@thoughtworks.com"
Assert.assertEquals("atryyang@thoughtworks.com", (rows(0) "td")(1).text.trim)

// Text in first cell in second row should be "John Smith"
Assert.assertEquals("John Smith", (rows(1) "td")(0).text.trim)

// Text in second cell in second row should be "john.smith@gmail.com"
Assert.assertEquals("john.smith@gmail.com", (rows(1) "td")(1).text.trim)
}
[/code]

Note that the test case performs a transformation from ContactCommand to Id instead of to Future. Id is defined as type Id[A] = A, so such a transformation provides a mapping from ContactCommand[A] to A, which means it will fetch data A immediately, in a synchronous manner, according to the specific command ContactCommand.

You can find the complete interpreter code example at the part2-free-interpreter tag of git://github.com/ThoughtWorksInc/play-each-example.git.

Bad business transparency

By introducing the command pattern and Scalaz, we implemented a "Functional Core, Imperative Shell" approach, and achieved both asynchrony and testability. Unfortunately, the contactsScript method is quite obscure. This method is written in a functional programming flavour, which consists of many higher ordered functions flatMap, map and traverseM. It is very different from the original contactsXhtml, and cannot reflect the acceptance criteria individually, as the original did. As mentioned before, the code in the core layer contains all the business logic and will grow continuously. I doubt that the maintainers, usually normal object-oriented programmers or domain experts, would be able to understand such a strange code base.

Fake procedure paradigm

In order to regain business transparency, we created a library Each.

This library converts native imperative syntax to scalaz's monadic expressions.

So, finally we rewrote our contactsScript like this:

[code lang=”scala”]
def contactsScript: Script[xml.Elem] = throwableMonadic[Script] {
val emailList = toScript(GetEmailList).each
<html>
<body>
<table>
{(for {
email <- emailList.monadicLoop
if email.matches( """[a-z.-_]+@[a-z.-_]+""")
} yield {
<tr>
<td>
{toScript(GetContactNameByEmail(email)).each}
</td>
<td>
{email}
</td>
</tr>
}): List[xml.Element]}
</table>
</body>
</html>
}
[/code]

As you see, the new contactsScript looks pretty much like the synchronous version of ContactController.contactsXhtml. The point is that throwableMonadic is a macro, which accepts an expression block that looks like synchronous imperative style and converts every .each call in the block into a bind/map call. As a result, the final bytecode that throwableMonadic generates is almost the same as the previous hand-written, functional, monadic contactsScript. In this way, the expressions written by a programmer are able to reflect the procedure-oriented business logic, while still keeping the functional programming core, and still be easily testable while having asynchronous behaviour.

You can find the complete interpreter code example with the help of Each at part2-each-interpreter tag of git://github.com/ThoughtWorksInc/play-each-example.git.

Cheat Sheet

In this blog, we have learnt how to use command pattern with help of Each. You can review them in a cheat sheet that contains some comparison between the direct style, underlying free monad interpreter and Each-style free monad interpreter for some common tasks.

Direct style Underlying free monad interpreter Each style free monad interpreter
Declaring a method of a script
def op: Result = {
  ???
}
def op: Script[Result] = {
  ???
}
def op: Script[Result] = monadic[Script] {
  ???
}
Evaluate a script
val result = op()
op().map { result =>
  ???
}
val result = op().each
Evaluate multiple scripts one by one
val result1 = op1()
val result2 = op2(result1)
op1().flatMap { result1 =>
  op2(result1).map { result2 =>
    ???
  }
}
val result1 = op1().each
val result2 = op2(result1).each
Evaluate scripts in a `for` loop
for (i <- List(1, 3, 5)) {
  op(i)
}
List(1, 3, 5).traverse_ { i ->
  op(i).map(_ => ())
}
for (i <- List(1, 3, 5).monadicLoop) {
  op(i).each
}
Evaluate scripts in a `for`/`yield` comprehension
val all = for {
  i <- List(1, 3, 5)
} yield {
  op(i)
}
List(1, 3, 5).traverseM { i =>
  op(1)
}.map { all =>
  ???
}
val all = (for {
  i <- List(1, 3, 5).monadicLoop
} yield {
  op(i).each
}).underlying
Evaluate scripts in a `for`/`yield` comprehension with a filter
val all = for {
  i <- List(1, 3, 5)
  if i != 3
} yield {
  op(i)
}
List(1, 3, 5).traverseM { i =>
  if (i != 3) {
    op(1)
  } else {
    List.empty[Int].point[Script]
  }
}.map { all =>
  ???
}
val all = (for {
  i <- List(1, 3, 5).monadicLoop
  if i != 3
} yield {
  op(i).each
}).underlying

Links

Credits

Thanks for Ken Scambler who reviewed this blog. Ken wrote the original blog about Scalaz's free monad and command pattern, though he was too embarrassed to name the pattern.

More from the blog

From Teaching to Software Development – A Year On

Category: Career stories
Published on

A vision of the future is a bet we are prepared to make

Category: Tech
Published on

One year at REA: Lessons, Growth, and Gratitude

Category: Life at REA
Published on

Introducing Argonaut – Part Three.

Category: Tech
Published on

Introducing Argonaut – Part Two.

Category: Tech
Published on

Introducing Argonaut – Part One.

Category: Tech
Published on

View all articles