Thursday, April 18, 2024
HomeProgrammingMaking Mocking Libraries Mock Themselves | by Sam Cooper | Feb, 2023

Making Mocking Libraries Mock Themselves | by Sam Cooper | Feb, 2023


Photograph by Nijwam Swargiary on Unsplash

Mocking libraries are highly effective instruments that may swap actual objects for pretend ones in unit checks, isolating the code underneath check from its dependencies. With the assistance of Java bytecode manipulation instruments like ByteBuddy, a library like Mockito or MockK can swap out the performance of just about any code on the JVM classpath.

So if the mocking framework code can stealth-drop a alternative for any piece of code within the software, what occurs once you level it at itself? What might probably go fallacious?

I’m testing this in Kotlin with two frameworks: MockK and Mockito. For the primary problem, let’s begin easy. Does mocking the mocking library even work?

mockStatic(Mockito::class.java, Solutions.CALLS_REAL_METHODS)
println(mockingDetails(Mockito::class.java).isMock)

Right here, I’m asking Mockito to mock itself, after which asking it whether or not it now thinks the Mockito class is a mock. Certain sufficient, it prints true. Cool! Now for MockK. This can be a little more durable as a result of as an alternative of getting a single class to mock, we’ve got a bunch of top-level features. I settled on mocking the file that comprises these features.

val mockk = Class.forName("io.mockk.MockKKt")
mockkStatic(mockk.kotlin)
println(isMockKMock(mockk, staticMock = true))

Success! A minimum of it prints true, so we all know we’ve mocked one thing.

One on a regular basis use of mocks is to file operate invocations and their arguments so {that a} check can confirm {that a} explicit operate was referred to as on the proper time. Can the mock framework spy on calls to its personal features?

First, I’ll create a easy mock Provider<String> that returns "foo". I’ll name the provider methodology and use the mocking library to confirm that operate name befell. Then comes the actual check: verifying that the verifying befell.

val staticMock = mockStatic(Mockito::class.java, CALLS_REAL_METHODS)
val check = mock(Provider::class.java) { "foo" }
println(check.get())
confirm(check).get()
staticMock.confirm { confirm(check) }

Mockito takes a commanding lead immediately, crusing by means of the check as if this was a superbly regular and smart factor to do. I even checked that it truly is performing the verification and that it fails if the invocation didn’t occur precisely as anticipated. How does MockK evaluate?

mockkStatic("io.mockk.MockK")
val check = mockk<Provider<String>> { each { get() } returns "foo" }
println(check.get())
confirm { check.get() }
confirm { confirm(verifyBlock = any()) }

MockK begins to battle right here, throwing an AbstractMethodError about how the article handed to verifyBlock doesn’t implement one among its required features. Really, it throws the identical error even when I don’t embody the preliminary mockkStatic name, so it seems prefer it merely isn’t attainable to nest calls to confirm. Passing an precise lambda rather than the any matcher doesn’t assist both, producing a distinct exception. If you will discover a technique to make this work, go away a remark!

The rating is 2–1 to Mockito. Can MockK claw it again within the remaining spherical?

The opposite widespread motive to make use of a mock is to offer dummy behaviour rather than the actual performance. How will the mocking libraries do after we ask them to stub their very own code? Place your bets now.

mockStatic(Mockito::class.java, CALLS_REAL_METHODS)
.`when`<Any> { mock(any(Class::class.java)) }
.thenReturn(Provider { "Imposter!" })
println(mock(Provider::class.java).get())

To maintain issues easy, I’m not utilizing mockito-kotlin, therefore the backticks required within the name to when. On this check, I’ve informed Mockito to return my imposter Provider object each time anyone tries to mock something. Possibly I ought to have anticipated it by now, however I used to be nonetheless fairly stunned to search out that this works with no drawback. The code runs with no errors and even prints the anticipated textual content "Imposter!". Is Mockito really simply magic?

I’m not assured about MockK’s probabilities, although. All these top-level inline features make issues messy.

mockkStatic("io.mockk.MockK")
each { mockk<Any>(block = any()) } returns Provider { "Imposter!"}
println(mockk<Provider<String>>().get())

Certain sufficient, it’s that pesky AbstractMethodError once more. At this level, I made a decision to dig into the MockK implementation and see if I might get previous that. Since I’m on the JVM, the precise implementation of the mockk operate is supplied by a category referred to as JvmMockKGateway. Mocking that class could be extra much like what I used to be doing with Mockito, and may stage the enjoying discipline a bit.

mockkObject(JvmMockKGateway)
each {
JvmMockKGateway.defaultImplementation.mockFactory
.mockk<Any>(any(), any(), any(), any(), any())
} returns Provider { "Imposter!" }
println(mockk<Provider<String>>().get())

It was a worthy try, however this one fails with a StackOverflowError. To be trustworthy, that’s precisely what I’d count on to occur once you ask a mocking framework to assault itself, and I’m amazed it by no means occurred in my experiments with Mockito. I assume MockK is destined to solely ever be used for really smart issues.

This can be a dumb factor to do, however I do have some extent. Mocking frameworks like MockK and Mockito provide two very distinct sorts of performance.

  • Features like mock(…) create a brand new object that may be handed to a operate or constructor rather than the actual factor. Creating the mock occasion doesn’t change the behaviour of every other objects or present code.
  • Then again, features like mockkObject or mockStatic change the behaviour of one thing that already exists. They can be utilized to tear out and exchange the implementation of a top-level operate or a whole class and its constructor. That is typically referred to as static mocking.

Static mocking makes it attainable to do dumb issues, like having the mocking framework mock itself. I don’t like static mocks. By their nature, constructors and top-level features might be referred to as from wherever at any time. Mocking them enables you to make sweeping modifications to system behaviour that may have unintended penalties nicely past the code you had been initially attempting to check.

The opposite day I got here throughout an actual instance that had me scratching my head for a while. A check failed with an odd stacktrace that appeared totally unrelated to what the check was doing. Finally, I tracked the issue all the way down to a static mock. The code underneath check was producing an object containing a timestamp.

Within the check setup, the system Calendar was mocked to make use of a hard and fast timestamp which might then be checked within the check’s assertion. The issue was the check framework additionally initialised a logger, which was attempting to put in writing a log message that included a timestamp. The misbehaving Calendar made the logger very confused, and the check framework crashed.

In that instance, one resolution would have been to go a Clock object to the constructor of the code underneath check. That method, it could have been straightforward to mock the person Clock object with out affecting anything within the software.

At all times search for methods to inject particular person mock objects as an alternative of counting on static mocks. Utilizing a static mock is like attempting to go a math check by looking down and altering each copy of the textbook. Sure, learning for the check may take some effort, however I guess it’ll create much less hassle in the long term.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments