JakeWharton / timber

A logger with a small, extensible API which provides utility on top of Android's normal Log class.
https://jakewharton.github.io/timber/docs/5.x/
Apache License 2.0
10.46k stars 962 forks source link

Unit testing with Mockk fail with multiples updates #439

Closed jdudu007 closed 3 years ago

jdudu007 commented 3 years ago

Hi @JakeWharton and thank you for your super lib.

Recently I have decided to update all my third-party and my tools :

I can't find by myself wich one have cause the trouble I am working on but here is my issue :

For my unit testing purpose, I have to mock the static Timber class to just run the log methods in the BeforeEach method :

mockkStatic(Timber::class)
justRun { Timber.plant(capture(treeSlot)) }
justRun { Timber.v(any<String>(), any()) }
justRun { Timber.d(any<String>(), any()) }
justRun { Timber.d(any<String>()) }
justRun { Timber.e(any<String>(), any()) }
justRun { Timber.e(any<String>()) }

My class have the annotation @ExtendWith(MockKExtension::class) from Junit 5 : import io.mockk.junit5.MockKExtension

If I run a test that make a call to Timber.v("Message"), I have now this error :

Failed matching mocking signature for

left matchers: [any()]
io.mockk.MockKException: Failed matching mocking signature for

left matchers: [any()]
    at io.mockk.impl.recording.SignatureMatcherDetector.detect(SignatureMatcherDetector.kt:99)
    at io.mockk.impl.recording.states.RecordingState.signMatchers(RecordingState.kt:39)
    at io.mockk.impl.recording.states.RecordingState.round(RecordingState.kt:31)
    at io.mockk.impl.recording.CommonCallRecorder.round(CommonCallRecorder.kt:50)
    at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:63)
    at io.mockk.impl.eval.EveryBlockEvaluator.every(EveryBlockEvaluator.kt:30)
    at io.mockk.MockKDsl.internalEvery(API.kt:92)
    at io.mockk.MockKKt.every(MockK.kt:98)
    at io.mockk.MockKKt.justRun(MockK.kt:106)
    at com.arkea.axolotl.android.monitoring.logger.timber.MonitorTimberUnitTest.setUp(MonitorTimberUnitTest.kt:59)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)

I have try multiple things to make it work as before but without succeed.

Any help will be grantly appreciated, thanks you so much.

jdudu007 commented 3 years ago

Hi @JakeWharton and thank you for your super lib.

Recently I have decided to update all my third-party and my tools :

  • Android studio Arctic Fox
  • Java 11 (jdk 11.0.11+9)
  • Gradle wrapper 7.1.1
  • AGP 7.0.0
  • Kotlin 1.5.21
  • Mockk 1.12.0
  • Timber 5.0.1

I can't find by myself wich one have cause the trouble I am working on but here is my issue :

For my unit testing purpose, I have to mock the static Timber class to just run the log methods in the BeforeEach method :

mockkStatic(Timber::class)
justRun { Timber.plant(capture(treeSlot)) }
justRun { Timber.v(any<String>(), any()) }
justRun { Timber.d(any<String>(), any()) }
justRun { Timber.d(any<String>()) }
justRun { Timber.e(any<String>(), any()) }
justRun { Timber.e(any<String>()) }

My class have the annotation @ExtendWith(MockKExtension::class) from Junit 5 : import io.mockk.junit5.MockKExtension

If I run a test that make a call to Timber.v("Message"), I have now this error :

Failed matching mocking signature for

left matchers: [any()]
io.mockk.MockKException: Failed matching mocking signature for

left matchers: [any()]
  at io.mockk.impl.recording.SignatureMatcherDetector.detect(SignatureMatcherDetector.kt:99)
  at io.mockk.impl.recording.states.RecordingState.signMatchers(RecordingState.kt:39)
  at io.mockk.impl.recording.states.RecordingState.round(RecordingState.kt:31)
  at io.mockk.impl.recording.CommonCallRecorder.round(CommonCallRecorder.kt:50)
  at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:63)
  at io.mockk.impl.eval.EveryBlockEvaluator.every(EveryBlockEvaluator.kt:30)
  at io.mockk.MockKDsl.internalEvery(API.kt:92)
  at io.mockk.MockKKt.every(MockK.kt:98)
  at io.mockk.MockKKt.justRun(MockK.kt:106)
  at com.arkea.axolotl.android.monitoring.logger.timber.MonitorTimberUnitTest.setUp(MonitorTimberUnitTest.kt:59)
  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.base/java.lang.reflect.Method.invoke(Method.java:566)
  at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
  at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)

I have try multiple things to make it work as before but without succeed.

Any help will be grantly appreciated, thanks you so much.

I finally found the answer. Now timber delegate its static log methods to its Companion object Forest.

So my mocks become :

 mockkObject(Timber.Forest)
 justRun { Timber.Forest.plant(capture(treeSlot)) }
 justRun { Timber.Forest.v(any<String>(), any()) }
 justRun { Timber.Forest.d(any<String>(), any()) }
 justRun { Timber.Forest.e(any<String>(), any()) }

it works, hope this gonna help someone :)

JakeWharton commented 3 years ago

There should be no reason to mock timber and if I could somehow prevent it I would. If you want to assert logging occurred then plant a Tree which appends to an ArrayList and check for items in the list. This removes your implementation detail knowledge and would have allowed your test to continue to pass across the new version.

jdudu007 commented 3 years ago

Thanks for your comment @JakeWharton but my clean architecture is designed in a way I have to mockk Timber. I have a module called monitor that expose an interface and Timber is one of the possibility implementation for logging.

To test my monitor logic without consider the choosen solution, I have to mock it and just verify I call Timber object.

I have found the solution so it's ok but it's good to know for others.

Regards :)

pverma17 commented 11 months ago

Hey @jdudu007 Could you please leave the reference of the solution here as well to help the others? Thanks :)