Photo by Susan Yin on Unsplash
Static method is bad, or at least, when it’s not pure.
Pure, in this context, means that:
Why is that bad?
Because whoever consumes that impure static method will not be easy to test. If the static method is pure it might be fine since you can predict its behavior and you don’t need to mock them (less mocking is great!), just asserting the return value is enough. But the impure one, it’s a different story.
If the code is under your control, you can and should avoid impure static method. But if you have to use static method from a library or framework and you can’t avoid them, what can you do?
For example, we want to send sms notification using Twillo.
Twillo SDK provides static method Message.creator
which is a factory method for constructing MessageCreator
. The method that actually makes a request to Twillo to send sms is messageCreator.create(restClient)
.
Even though Message.creator
is pure, but messageCreator.create(restClient)
which is the method of the result of Message.creator
is not. So we need to mock the create
method by first stubbing return value of Message.creator
. But since Message.creator
is static, we don’t have our way in.
One might say: we can create a wrapper class around that static method, so that we can mock the wrapper instead.
But testing is all about increasing confidence in your code. Adding another level of indirection just for testing doesn’t increase confidence level in your system under test and adding more complexity to your code base.
So if adding wrapper helps you improving team’s coding experience consuming the API and it is being use a lot then it might worth investment, not just for the sake of testing.
Then, is there a leaner way?
We want to somehow create a path to replace Message.creator
with something else. In Kotlin and a many languages these days treat function as a first-class citizen. The smallest reusable component is no more class but function. So you can pass function around like any other object.
In a strict functional and mathematical sense, function must be pure. But in Kotlin context, seems like function is allowed to be impure.
The difference between method and function in Kotlin is, while method is associated to an object, function doesn’t need to. You can see that the fun
keyword which refers to function everywhere regardless of being a method declaration or top-level function declaration, which means function is a super-set of method, at least in this context.
But what is Higher-order function?
Higher-order function is basically means the function that has function as parameter(s) or returns function.
Even though function that does not have function as parameter(s) and not returning function is called first-order function, the term higher-order is being used here instead of second-order or third-order because, in theory, we can nest “function that has function as parameter(s) or returns function” infinitely many level. So that it’s higher than second or third order.
Let’s get back to our main topic. So we want to inject Message.creator
into notify
and we can achieve that by making notify
a higher-order function so that we can pass Message.creator
in as an argument.
In order to do that, first step, we need get the type right.
Let’s figure out the type of this particular function. Here is the implementation of the original creator
in the SDK:
So it has a type
(PhoneNumber, String, String) -> MessageCreator
which means parameter types on the left side of the arrow “->
” and return type on the right side.
Now let’s define our typealias
to refer to this function type and name it for readability.
But now, we’ve just changed our interface, which is not really nice since normally we don’t need to pass creator
to notify
except in test.
In Kotlin, we can avoid that by providing default argument.
So now, our existing interface stays the same and we have our way in!
I’m using Spek and mockito-kotlin here.
So we define a creator
that returns a mocked MessageCreator
that stub create
method to return Message
we want.
You might ask, at line 22:
Message
constructor? => It’s private.So we end up using fromJson
instead.
Edit 14 July 2018: David Kirchner point out that mockito 2 can mock _final_
class and method already. See full response here
And I reference to creator
function by using ::creator
, it will regard as a value. The other way, we can use lambda and assign it to val
instead of fun
.
By using lambda, we can just ignore the arguments using _
since we are not using it in this context. This is not possible if we define it with fun
.
For more info about how all these functions and lambdas work, you can find out in Kotlin documentation.
Now, we might want to verify if the creator
is being called with correct arguments. The problem is mockito’s verify
only supports method call verification. How can we verify creator
that is not associate to any object?
But turns out, function is actually just a class with invoke
method, so instead of creating function on our own, we can mock the function type. That way, mockito can verify the invoke
method of that function if it’s being called by correct arguments. Here is how function being defined in it’s standard library.
Now we know that we can mock our function to verify the call and we can also stub the return value. Let’s do both!
As you can see, we can verify our function call and stubbing the return value of the static method without hacking the language.
We are leveraging higher-order function and default argument feature of the language to allow us to inject static method into a function that we want to test so that we can mock/stub it while retaining the old interface. Any language that has both features can also use the same trick as well.
I hope you’ve got the idea.
Thanks for reading! Enjoy testing :D
Follow me on twitter @ibossptk