Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing helper or non-interface traits in Scala

This question is about dealing with testing of classes which mix in non-interface traits, that is traits containing some functionality. When testing, the class functionality should be isolated from the functionality provided by the mix-in trait (which is supposedly tested separately).

I have a simple Crawler class, which depends on a HttpConnection and a HttpHelpers collection of utility functions. Let's focus on the HttpHelpers now.

In Java, HttpHelpers would possibly be a utility class, and would pass its singleton to Crawler as a dependency, either manually or with some IoC framework. Testing Crawler is straightforward, since the dependency is easy to mock.

In Scala it seems that a helper trait is more preferred way of composing functionality. Indeed, it is easier to use (methods automatically imported into the namespace when extending, can use withResponse ... instead of httpHelper.withResponse ..., etc.). But how does it affect testing?

This is my solution I came up with, but unfortunately it lifts some boilerplate to the testing side.

Helper trait:

trait HttpHelpers {
  val httpClient: HttpClient
  protected def withResponse[A](resp: HttpResponse)(fun: HttpResponse => A): A = // ...
  protected def makeGetRequest(url: String): HttpResponse = // ...
}

Code to test:

class Crawler(val httpClient: HttpClient) extends HttpHelpers {
  // ...
}

Test:

// Mock support trait
// 1) Opens up protected trait methods to public (to be able to mock their invocation)
// 2) Forwards methods to the mock object (abstract yet)
trait MockHttpHelpers extends HttpHelpers {
  val myMock: MockHttpHelpers
  override def makeGetRequest(url: String): HttpResponse = myMock.makeGetRequest(url)
}

// Create our mock using the support trait
val helpersMock = Mockito.mock(classOf[MockHttpHelpers])

// Now we can do some mocking
val mockRequest = // ...
Mockito when (helpersMock.makeGetRequest(Matchers.anyString())) thenReturn mockRequest

// Override Crawler with the mocked helper functionality
class TestCrawler extends Crawler(httpClient) with MockHttpHelpers {
  val myMock = helpersMock
}

// Now we can test
val crawler = new TestCrawler()
crawler.someMethodToTest()

Question

This approach does the work, but the need to have a mock support trait for each helper trait is a bit tedious. However I can't see any other way for this to work.

  • Is this the right approach?
  • If it is, could its goal be reached more efficiently (syntax magic, compiler plugin, etc)?

Any feedback is welcome. Thank you!

like image 948
ron Avatar asked Aug 14 '11 21:08

ron


People also ask

Is Scala trait an interface?

One way to use a Scala trait is like the original Java interface , where you define the desired interface for some piece of functionality, but you don't implement any behavior.

Are there interfaces in Scala?

Here is one major difference that Scala has from Java: there are no interfaces. There is no interface keyword. Yes, even though Scala is primarily a JVM language and often touted as a "better Java", it doesn't have interfaces. Scala has something better: traits.

What is Scala unit testing?

by Harit Himanshu. ScalaTest is a well-known choice for testing Scala projects. Write tests to provide evidence that the project works as expected. These tests also serve as a communication and learning tool for other developers and stakeholders in your organization.


1 Answers

You can write an Helper mock trait which should be mixed with HttpHelpers and override its methods with mock equivalent:

trait HttpHelpersMock { this: HttpHelpers =>

  //MOCK IMPLEMENTATION
  override protected def withResponse[A](resp: HttpResponse)(fun: HttpResponse => A): A = // ...

  //MOCK IMPLEMENTATION
  override protected def makeGetRequest(url: String): HttpResponse = // ...
}

Then, when testing crawler, you mix the mock trait at instantiation:

val crawlerTestee = new Crawler(x) with HttpHelpersMock

And the mock methods will just replace the helper methods in instance crawlerTestee.

Edit: I don't think its a good idea to test how a class interacts with an helper trait. In my opinion, you should test Crawler behavior and not its internal implementation detail. Implementations can change but the behavior should stay as stable as possible. The process I described above allows you to override helper methods to make them deterministic and avoid real networking, thus helping and speeding tests.

However, I believe it make sense to test the Helper itself, since it may be reused elsewhere and has a proper behavior.

like image 121
paradigmatic Avatar answered Sep 20 '22 12:09

paradigmatic