The Kotlin mocking library mockk lets's you mock a class like this:
class Foo {
fun foo() = "foo"
}
val foo = mockk<Foo>()
every { foo.foo() } returns "bar" // ๐ make the foo mock return "bar"
foo.foo() shouldBe "bar"
It also throws an error if you try to mock an instance that is no mock:
val foo = Foo()
every { foo.foo() } returns "bar" // ๐ trying to mock an instance that is no mock
Missing mocked calls inside every { ... } block: make sure the object inside the block is a mock
But if you look at the following example, mocking non-mock instances seems to work:
class Bar {
fun bar() = "bar"
}
class Foo(private val bar: Bar) {
fun foo() = bar.bar()
}
val foo = Foo(mockk())
every { foo.foo() } returns "baz" // ๐ make the foo instance return "baz"
foo.foo() shouldBe "baz"
The above code works! foo
is no mock but the foo.foo()
call actually returns baz
.
During the every
call, mockk switches to the recording mode.
It records calls to mocks and assigns the return value to the recorded invocation.
Calls to non-mocks are not recorded, but the invocation takes place nonetheless.
In the example foo.foo()
is called. foo.foo()
internally calls bar.bar()
whereat bar
is a mock.
That is, the bar.bar()
call is recorded and the return value baz
is assigned to the bar.bar()
call.
In short, syntactically it looks like we are mocking the non-mock foo
,
but in reality we are accidentally interacting with the mock bar
which foo
delegates to.
This becomes more obvious when we have Foo.foo()
modify the output of bar.bar()
:
class Bar {
fun bar() = "bar"
}
class Foo(private val bar: Bar) {
fun foo() = "foo-" + bar.bar() // ๐ prepend `foo-` to its return value
}
val foo = Foo(mockk())
every { foo.foo() } returns "baz"
foo.foo() shouldBe "foo-baz" // ๐ only the returned value of `bar.bar()` is modified
Mocking non-mocks using mockk is not possible.
If you find yourself in a situation where you think you are mocking a non-mock, you are probably interacting with a mock that the non-mock delegates to.