Testing without mocking in Scala

Posted by Jessica Kerr on July 28, 2015

For unit testing in Java, mocking frameworks replace classes necessary for the code under test, but not under test themselves. These mock frameworks don’t transfer easily to Scala. That’s OK: the functional side of Scala can make mocking unnecessary. As someone told me the other day at PolyConf: mocks are the sound of your code crying out, “please structure me differently!”

Don’t use mocks? Structure code differently? Easier said than done. What follows is a practical example of removing the need for a mock object, and at the same time separating concerns of interface and business logic.

Say there’s an IdentityService that returns a username based on an access token. Internally, it calls out to AccessTokenService, retrieving information about the access token. Then it interprets the result: success provides an identity; anything else means proceed anonymously (return no identity). The code looks like:


class IdentityClient(jsonClient: JsonClient)  {

  def fetchIdentity(accessToken: String) : Future[Option[Identity]] = {
    jsonClient.getWithoutSession(
      Path("identities"),
      Params("access_token" -> accessToken)
    ).map {
      case JsonResponse(OkStatus, json, _, _) => Some(Identity.from(json))
      case _ => None
    }
  }
}

The tests want to say, “If the inner call returns success, provide the returned identity; if it fails, return none.” To unit-test that, we need to mock the JsonClient, and its getWithoutSession method, and check the arguments… ugh, mocking.

The secret here is to recognize that part of the method under test is about the interface, and part of it is business logic.

The interface is only testable in integration tests. That’s where we check our assumptions about the path structure, the input and the output of the other service. The business logic part of this is unit-testable once we separate the two.

Ports and Adapters

Instead of passing in a general JsonClient, let’s pass in a function that contains all the interface code. That function needs an access token, and it returns a future response.


class IdentityClient(howToCheck: String => Future[JsonResponse]) {

  def fetchIdentity(accessToken: String) : Future[Option[Identity]] = {
    howToCheck(accessToken).map {
      case JsonResponse(OkStatus, json, _, _) => Some(IdentityMapper(json))
      case _ => None
    }
  }
}

Meanwhile, the real interface code is shipped off to a handy object somewhere:


object RealAccessTokenService {
  def reallyCheckAccessToken(jsonClient:JsonClient)(accessToken: String): Future[JsonResponse] = 
    jsonClient.getWithoutSession(
    Path() / "identity",
    Params("access_token" -> accessToken)
  )
}

The production code can instantiate the AccessToken client using that object, but the test is free to provide a fake function implementation, without duplicating any specifics about this particular interface:


it("returns an Identity when we have it") {
  val jsonBody = Map("identity" -> Map("id" -> "external_username")).toJson
  val identityClient 
    = new IdentityClient(_ => Future(JsonResponse(OkStatus, jsonBody)))

  Await.result(identityClient.fetchIdentity("an_access_token"), 1.second) ===
    Some(Identity("external_username"))
}

This test constructs the expected response and then provides a function that returns that, no matter what. It isn’t checking the arguments, although it could. We can pass a function that does whatever we want, for the purposes of our test. There’s no JsonClient object to mock. (Technically, the function we passed is a fake, which is different from a mock. It works here.)

This is a minimal example, and the test isn’t perfect. Yet, it shows how passing a “how” instead of passing an object can make testing easier in Scala. Check the sample code before and after to see the difference.

This example illustrates a ports-and-adapters architecture. By removing the interface code, we created a port – like a hole, like a Java interface. Then the RealJsonClient contains an adapter: a plug for the hole that hooks up to a real-life system. The function passed in the test is an adapter that fits the same hole.

Or: Flow of Data

The ports-and-adapters style lets us drop in different implementations for I/O that happens in the middle of our code. If we can avoid I/O in the middle of the code, even better.

There’s a cleaner way of restructuring this same code, because it’s possible to view it as: gather data, then make decisions, then output.

input, transformation, output

Instead of passing in how to call the AccessTokenService, why not make the call and pass the results into the function under test?


object dentityClient  {
  def fetchIdentity(accessTokenInfo: Future[JsonResponse]) : Future[Option[dentity]] = {
   accessTokenInfo.map {
      case JsonResponse(OkStatus, json) => Some(Identity.from(json)){
      case _ => None{
    }
  }
}

The function is data-in, data-out. No need to instantiate a class; an object will do. The code is even easier to test now. No mocks, no fakes, construct some input and pass it in.


import dentityClient._

it("returns an Identity when we have it") {
  val jsonBody = Map("identity" -> Map("id" -> "external_username")).toJson
  val accessTokenInfo = Future(JsonResponse(OkStatus, jsonBody))

  Await.result(fetchIdentity(accessTokenInfo), 1.second) ===
  Some(Identity("external_username"))
}

In the real world, we use the same RealAccessTokenService to gather the input before calling our data-in, data-out function. Instead of passing “how to gather input” we’re passing the input as data. This is the simplest structure. Sample code here.

Whenever you see mocking in Scala, look for an opportunity to separate decision-making code from interface code. Consider these styles instead.

Thanks to Duana for asking me these questions and providing the example.

posted on July 28, 2015 by
Jessica Kerr