toothpick icon indicating copy to clipboard operation
toothpick copied to clipboard

Mockk/TP issue

Open afaucogney opened this issue 4 years ago • 7 comments

After a bunch of analysis, we identify what's wrong between TP and Mockk.

Here is the app code:

class Tooth @Inject constructor() {

    @Inject
    lateinit var pick: Pick

    fun foo() = pick.bar()

}

class Pick @Inject constructor() {

    @Inject
    lateinit var smoothie: ISmoothie

    fun bar() = smoothie.olive()

}

interface ISmoothie {
    fun olive(): Int
}

class Smoothie @Inject constructor() : ISmoothie {
    override fun olive() = 42
}

Here the full TP case, no mock : OK

class FullTP {

    val toothPickRule = ToothPickRule(this, "Foo")
        @Rule get

    lateinit var tooth: Tooth

    @Before
    fun setUp() {
        toothPickRule.scope.installModules(module {
            bind(ISmoothie::class.java).to(Smoothie::class.java)
        })
        toothPickRule.inject(this)
        tooth = toothPickRule.getInstance(Tooth::class.java)
    }

    @Test
    fun calculateAddsValues1() {
        Assert.assertEquals(42, tooth.foo())
    }
}

Now, if I want to mock Smoothie:

//Should work
class SmoothieMockFailed {

    val toothPickRule = ToothPickRule(this, "Foo")
        @Rule get

    lateinit var tooth: Tooth

    @MockK
    lateinit var smoothie: ISmoothie

    @Before
    fun setUp() {
        MockKAnnotations.init(this)
        toothPickRule.inject(this)
        tooth = toothPickRule.getInstance(Tooth::class.java)
    }

    @Test
    fun calculateAddsValues1() {
        every { smoothie.olive() } returns 41
        Assert.assertEquals(41, tooth.foo())
    }
}

The error is the following : toothpick.locators.NoFactoryFoundException: No factory could be found for class fr.afaucogney.mobile.android.app.proapp.ISmoothie. Check that the class has either a @Inject annotated constructor or contains @Inject annotated members.

This testcase should work.

To hack it and make it work, we have to use this testcase:

class HackOne {

    val toothPickRule = ToothPickRule(this, "Foo")
        @Rule get

    //    @Inject
    lateinit var tooth: Tooth

    @MockK
    @field:MockK
    lateinit var smoothie: ISmoothie

    @Before
    fun setUp() {
        MockKAnnotations.init(this) // turn relaxUnitFun on for all mocks
        toothPickRule.inject(this)
        tooth = toothPickRule.getInstance(Tooth::class.java)
    }

    @Test
    fun calculateAddsValues1() {
        every { smoothie.olive() } returns 41
        Assert.assertEquals(41, tooth.foo())
    }
}

This one work because of dual annotation. @field:Mockk to let TP bind the ISmoothieMock and @Mockk to let Mock running the stub.

In fact the @Mockk annotation target property (or target nothing, and the property is by default) The @Mock target field, so TP succeed to do its binding

I do not know if this is the job of TP or Mockk to fix this. I have been in contact with the guy from mockk that fairly said "not very experienced with this annotations"

I will ask him to join.

afaucogney avatar Sep 18 '19 15:09 afaucogney

Here are the decompiled koltin to java code

When only @field:MockK

   @MockK
   @NotNull
   public ISmoothie smoothie;

When only @MockK

   @MockK
   public static void smoothie$annotations() {
   }

When both:

   @MockK
   @NotNull
   public ISmoothie smoothie;

   ...

   @MockK
   public static void smoothie$annotations() {
   }

afaucogney avatar Sep 18 '19 15:09 afaucogney

Hey @afaucogney thx for the detailed example!

The Mock annotation for easymock and mockito has Target = FIELD. That means that on the bytecode the annotation goes to the field. For MockK, the annotation MockK does not have a target, so it goes to the method.

Because of the issue https://github.com/stephanenicolas/toothpick/issues/334, we thought that the MockK annotation already worked. We should have added a unit test...

I see 2 options:

  • Mockk adds the target to the annotation to make it equivalent to Easymock and Mockito
  • We modify the rule to read methods of the type XYZ$annotations and MockK annotation. Then look for the field with name XYZ.

Taking into account that you are already in contact with the MockK author. Would you be able to ask him why he does not have a target and if he would be willing to do so? Otherwise, we can change the rule.

dlemures avatar Sep 22 '19 17:09 dlemures

What happens when an annotation has multiple targets ? I couldn't find any doc about that..

On Sun, Sep 22, 2019, 10:58 Daniel Molinero, [email protected] wrote:

Hey @afaucogney https://github.com/afaucogney thx for the detailed example!

The Mock annotation for easymock and mockito has Target = FIELD. That means that on the bytecode the annotation goes to the field. For MockK, the annotation MockK does not have a target, so it goes to the method.

Because of the issue #334 https://github.com/stephanenicolas/toothpick/issues/334, we thought that the MockK annotation already worked. We should have added a unit test...

I see 2 options:

  • Mockk adds the target to the annotation to make it equivalent to Easymock and Mockito
  • We modify the rule to read methods of the type XYZ$annotations and MockK annotation. Then look for the field with name XYZ.

Taking into account that you are already in contact with the MockK author. Would you be able to ask him why he does not have a target and if he would be willing to do so? Otherwise, we can change the rule.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/stephanenicolas/toothpick/issues/373?email_source=notifications&email_token=AAN7PXLCE6FUWLW53MDBPODQK6W5ZA5CNFSM4IYAVIAKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD7JLPJI#issuecomment-533903269, or mute the thread https://github.com/notifications/unsubscribe-auth/AAN7PXN2GLW5WM2VFTAWTJ3QK6W5ZANCNFSM4IYAVIAA .

stephanenicolas avatar Sep 22 '19 18:09 stephanenicolas

Yeah, i couldn't see any doc either. Just checking the generated bytecode 😕

dlemures avatar Sep 25 '19 14:09 dlemures

@afaucogney were you able to sync with the MockK author?

dlemures avatar Oct 18 '19 17:10 dlemures

Yes, but he is under project pressure, so he has only time to manage critical bug for now....

From a general matter, which should adapter to the other ! Di or Mock ? What is your opinion ?

Le ven. 18 oct. 2019 à 13:13, Daniel Molinero [email protected] a écrit :

@afaucogney https://github.com/afaucogney were you able to sync with the MockK author?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/stephanenicolas/toothpick/issues/373?email_source=notifications&email_token=ABSAEWPT7ID635INLCCWNELQPHVDXA5CNFSM4IYAVIAKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEBVF4VY#issuecomment-543841879, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABSAEWMVWWH46YNE2OJ6LOTQPHVDXANCNFSM4IYAVIAA .

afaucogney avatar Oct 18 '19 19:10 afaucogney

My question is why the annotation @MockK does not have a target. If it is something that they are missing, then they could fix it. If it is intended not to have it, we can fix it on our side.

dlemures avatar Oct 19 '19 21:10 dlemures