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.
Any feedback is welcome. Thank you!
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With