With the logic of TDD in mind and trying to understand how to write unit test’s, I am having trouble with Kotlin object. The test pass but I am unsure if this is actually the right test. I am trying to make sure that the Logger.i() method is called and that it saves into the database. But at the moment I am stuck at just its called part.
My Object
object Logger {
fun i(tag: String, msg: String, tr: Throwable? = null): Int {
insertIntoLogDatabase(createLogModel("i", tag, msg, tr))
return if (BuildConfig.DEBUG) Log.i(tag, msg, tr) else 0
}
private fun insertIntoLogDatabase(log: LogModel) {
//Insert into Log DB
logRepo.upsert(log)
}
private fun createLogModel(type: String, tag: String, msg: String, tr: Throwable?) = LogModel(0, type, tag, msg, if (tr != null) tr.message + "\n" + tr?.stackTrace.contentToString() else null)
fun setLogRepo(logRepo: LogRepository) {
this.logRepo = logRepo
}
}
with this, I know that I have to call Logger.setLogRepo(logRemp) to give the Logger access to the repo (and this works)
Where I am stuck is I am trying to unit test the Log.i method call
I have this
@Mock
lateinit var log: Logger
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
Logger.setLogRepository(logRepo)
}
@Test
fun `log i failed`() {
// When
log.i("Test", "Test1")
// Then
verify(log, times(1)).i("Test", "Test1")
}
I mean this works but is it correct (my gut tells me that something is wrong that I am not actually testing the Logger.i() method
please advise.
Thanks.
First, as far as I understand Mockito, you should use @Mock
annotation if you’re mocking an interface. In your case you’re spying on an object, so @Spy
would be the annotation to use.
Second, calling a method and then verifying it was called does not make much sense - unless method Logger.i(String, String)
throws an exception your test will always pass.
Uusually you would call method Logger.i(String, String)
and then verify if one of the methods that should be called down the line is actually called and if so, whether its called with correct arguments. In your case that would be methods: Logger.insertIntoLogDatabase(LogModel)
, Logger.createLogModel(...)
and Log.i(...)
.
Also, since method Log.i(...)
is called inside a conditional expression, you should have two tests - one where BuildConfig.DEBUG
is true and the other where it’s false - and verify if said method is called or not called as expected. This way your tests will cover all possible execution paths, which is a desired outcome in TDD.
Here’s an article on spying in Mockito for further reading: Mockito - Using Spies
And another one on using Mockito with Kotlin: Kotlin with Mockito
1 Like
Yes, do not mock the Logger. You want to use the real Logger. So your “when” should be Logger.i("Test", "Test1")
.
Create a mock log repository and verify that it was called with the correct value. Using the Mockito-Kotlin library, that would look like:
verify(logRepo).upsert(check {
assertEquals("i", it.type)
assertEquals("Test", it.tag)
assertEquals("Test1", it.msg)
})
1 Like
Thank you both for replying. Yes, first I was completely unaware of the Spy vs Mock (so thank you for the very helpful resources.
My solution was actually exactly as you had indicated
@Mock
lateinit var logRepo: LogRepository
@Test
fun log i failed
() {
// When
log.i("Test", "Test1")
// Then
verify(log, times(1)).upsert(any())
}
However to make the above work cause I tried using LogModel() inside the upsert but kept getting an error that things did not match (due to using a createDate that would always be different) I needed to add the following code from this site:
private fun <T> any(): T {
Mockito.any<T>()
return uninitialized()
}
private fun <T> uninitialized(): T = null as T
Just curious, Because, the BuildConfig.DEBUG is static final, how would I go about creating a test that would verify what happens when it’s set to false?
I mean I know I can create this test
assertThat(“Returned class is not an Int”, Log.i(“Test”, “Test”), isA(Int::class.java))
which verifies that an int is returned (which in either case is true). But for future knowledge and others who may be wonder also (let’s say I have a static final somewhere that the end result is different than the conditional expression equivalent, how would one go about
First, sorry for the long reply time - I was on vacation.
Second - unless the value of the constant is inlined during compilation, you can change it via reflection at the beginning of the test method.
While I don’y have an example on how to do it in a test (or in Kotlin), here’s how I used Java reflection to change options list (private static final if I recall correctly) in a H2 console in Spring Boot: https://gist.github.com/Forinil/b79d7a9ed122d2b26cfb9e310094eaa0
1 Like