Purpose
I want to test the result of a function of a class. And it returns an instance of an Android class, which should be mocked in a unit test since it seems to be stubbed in any unit test.
Example code
Here's a minimized example code of the simulated android class, AndroidData
, and the class to be tested, MyHelper
.
/**
* Should be mocked in a unit test.
*/
class AndroidData(val value: Float)
/**
* Target class to be tested.
*/
class MyHelper() {
fun createData(flag: Boolean): AndroidData {
// simplified logic
return if (flag) AndroidData(20f) else AndroidData(10f)
}
}
What I want to achieve is something like this(note that this is incorrect code):
class MyHelperTest : TestCase() {
@Test
fun testCreateData() {
mockkConstructor(AndroidData::class)
val valueSlot = slot<Float>()
every { constructedWith<AndroidData>(capture(valueSlot)) } // Type mismatch in IDE
val helper = MyHelper()
val returnedData = helper.createData(true)
assertTrue(returnedData.value >= 15f)
}
}
I want to test that returned AndroidData
instance has a correct value
, or matches some conditions. I cannot find any tutorial about this case or any correct way to implement it.
What I tried
These 2 test cases would both fail.
@Test
fun testCreateData1() {
mockkConstructor(AndroidData::class)
val helper = mockk<MyHelper>(relaxed = true)
val createdData = helper.createData(true)
println("createdData=$createdData") // AndroidData(child of #1#2)
println("createdData.value=${createdData.value}") // 0.0
// test if the argument in AndroidData's constructor was larger than 15
assertTrue(createdData.value >= 15f) // assertion failed
}
@Test
fun testCreateData2() {
mockkConstructor(AndroidData::class)
// raise error: Missing mocked calls inside every { ... } block: make sure the object inside the block is a mock
every {
constructedWith<AndroidData>(LargerThan15Matcher())
}
val helper = mockk<MyHelper>(relaxed = true)
val createdData = helper.createData(true)
println("createdData=$createdData") // AndroidData(child of #1#2)
println("createdData.value=${createdData.value}") // 0.0
// test if the argument in AndroidData's constructor was larger than 15
assertTrue(createdData.value >= 15f)
}
The testing idea was pretty easy and intuitive for Python unittest in my experience. Yet it seems impossible in Java or Android? I haven't tried the mockito
and roboletric
library because I was told that mockk
provides support on Android projects. Or I just haven't found the correct way to do this, or the whole testing idea is completely wrong? Please end my days of searching and struggle.
Update 0815
With @Karsten Gabriel 's suggestion, I made a bit of mod on the testCreateData2()
test case, but still it doesn't work. The createdData
seems not really an AndroidData
instance and has value
0.0.
@Test
fun testCreateData2() {
mockkConstructor(AndroidData::class)
// Missing mocked calls inside every { ... } block: make sure the object inside the block is a mock
every {
constructedWith<AndroidData>(LargerThan15Matcher()).value
} returns 1f
val helper = mockk<MyHelper>(relaxed = true)
val createdData = helper.createData(true)
println("createdData=$createdData") // AndroidData(child of #1#2)
println("createdData.value=${createdData.value}") // 0.0
// test if the argument in AndroidData's constructor was larger than 15
assertTrue(createdData.value >= 15f)
}
BTW, the LargerThan15Matcher
:
class LargerThan15Matcher : Matcher<Float> {
override fun match(arg: Float?): Boolean {
return if (arg == null) false else arg >= 15f
}
}
CodePudding user response:
It is not really clear to me what you are trying to achieve, but I can try to help you with what I think I understand.
Let's start with your test-case testCreateData1
:
- A relaxed mock in MockK tries to return a "simple value" (mostly "default" values or null if possible) for each function call of the mock.
- Therefore, your relaxed mock of
MyHelper
returnsAndroidData(0.0)
which does not satisfy your assertion.
Regarding test-case testCreateData2
:
every { ... }
alone is not a proper expression, but it is intended to specify what should happen on every...
. For instance, if you have a mockm
with a functionf
you could write something like the following:
every { m.f() } returns 42
every { m.f() } answers { callOriginal() }
- In pretty much the same way,
constructedWith<AndroidData>(LargerThan15Matcher())
is not a proper expression, because it is intended to specify what should happen with an AndroidData constructed with an argument matched by theLargerThan15Matcher
. For instance, if you want to say that thevalue
of such an object should always return 1 you could write
every { constructedWith<AndroidData>(LargerThan15Matcher()).value } returns 1f
Update 0815
Your updated test does not work due to an unfortunate combination of mocking:
- You mock each constructed element of
AndroidData
. - But at the same time you also have a (relaxed) mock of
MyHelper
which does not call the constructor ofAndroidData
, but instead it gives you some mock ofAndroidData
.
There are (at least) two possibilities to solve this:
- You do not mock
MyHelper
but only constructed values ofAndroidData
:
mockkConstructor(AndroidData::class)
every {
constructedWith<AndroidData>(LargerThan15Matcher()).value
} returns 20f
val helper = MyHelper()
val createdData = helper.createData(true)
assertTrue(createdData.value >= 15f)
- You mock
MyHelper
and you explicitly define how your mock should work:
val helper = mockk<MyHelper> {
every { createData(true) } returns AndroidData(20f)
}
val createdData = helper.createData(true)
// test if the argument in AndroidData's constructor was larger than 15
assertTrue(createdData.value >= 15f)
or
val helper = mockk<MyHelper> {
every { createData(true) } returns mockk {
every { value } returns 20f
}
}
val createdData = helper.createData(true)
assertTrue(createdData.value >= 15f)