Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regression starting in mockito-inline 4.7.0? #2860

Closed
5 tasks done
DaveTryon opened this issue Jan 11, 2023 · 5 comments
Closed
5 tasks done

Regression starting in mockito-inline 4.7.0? #2860

DaveTryon opened this issue Jan 11, 2023 · 5 comments

Comments

@DaveTryon
Copy link

DaveTryon commented Jan 11, 2023

We use mockito-inline in this test class. It works just great with mockito-inline version 4.6.1, but is broken with mockito-inline versions 4.7.0 and above. Specifically, when we try to mock out a method that accepts a gson ExclusionStrategies, the following Exception is thrown:

class [Lcom.google.gson.ExclusionStrategy; cannot be cast to class com.google.gson.ExclusionStrategy ([Lcom.google.gson.ExclusionStrategy; and com.google.gson.ExclusionStrategy are in unnamed module of loader 'app')
java.lang.ClassCastException: class [Lcom.google.gson.ExclusionStrategy; cannot be cast to class com.google.gson.ExclusionStrategy ([Lcom.google.gson.ExclusionStrategy; and com.google.gson.ExclusionStrategy are in unnamed module of loader 'app')
	at org.mockito.internal.stubbing.answers.AnswerFunctionalInterfaces$1.answer(AnswerFunctionalInterfaces.java:47)
	at org.mockito.internal.stubbing.StubbedInvocationMatcher.answer(StubbedInvocationMatcher.java:42)
	at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:103)
	at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
	at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:34)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:82)
	at org.mockito.internal.creation.bytebuddy.MockMethodAdvice.handle(MockMethodAdvice.java:134)
	at com.google.gson.GsonBuilder.setExclusionStrategies(GsonBuilder.java:431)
	at com.microsoft.accessibilityinsightsforandroidservice.ATFAResultsSerializer.<init>(ATFAResultsSerializer.java:49)
	at com.microsoft.accessibilityinsightsforandroidservice.ATFAResultsSerializerTest.prepare(ATFAResultsSerializerTest.java:83)

Looking at the commits that went into version 4.7.0, it seems like #2664 and #2685 are the only commits that would have changed anything related to the stack trace shown above. My guess would be #2664, since the issue it was intended to fix (#2644) reported a stack trace that is almost identical to what we're seeing (even though we aren't using varargs).

Here's a stripped down class for testing purposes--it works on mockito-inline version 4.6.1, and fails on any version 4.7.0 or above:

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.AdditionalAnswers;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
    @Mock GsonBuilder gsonBuilder;

    @Before
    public void prepare() {
        doAnswer(
                AdditionalAnswers.answer(
                        (ExclusionStrategy strategy) -> {
                            return gsonBuilder;
                        }))
                .when(gsonBuilder)
                .setExclusionStrategies(any());
    }

    @Test
    public void thisWorksBeforeMockitoFourPointSeven() {
        ExclusionStrategy e = new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes f) {
                return false;
            }

            @Override
            public boolean shouldSkipClass(Class<?> c) {
                return false;
            }
        };

        GsonBuilder g = gsonBuilder.setExclusionStrategies(e); // Throws if version > 4.6.1
        Assert.assertNotNull(g);
    }
}

check that

  • The mockito message in the stacktrace have useful information, but it didn't help
  • The problematic code (if that's possible) is copied here;
    Note that some configuration are impossible to mock via Mockito
  • Provide versions (mockito / jdk / os / any other relevant information)
  • Provide a Short, Self Contained, Correct (Compilable), Example of the issue
    (same as any question on stackoverflow.com)
  • Read the contributing guide
@TimvdLippe
Copy link
Contributor

I am not sure what is going on here, but looking at the test I feel like you want to use RETURNS_SELF (https://javadoc.io/static/org.mockito/mockito-core/4.11.0/org/mockito/Mockito.html#RETURNS_SELF) since you are dealing with a builder. In case you want to capture the argument, I recommend using an ArgumentCaptor instead. Would these two API's also suffice for your use case?

@DaveTryon
Copy link
Author

Thanks, @TimvdLippe! I've tried the changes you suggested and opened a PR. It definitely works with the current version of mockito and we'll see how my team feels about it. One hiccup with the change--I couldn't come up with a correct way to instantiate an ArgumentCaptor<JsonSerializer<Class>>. If you have a suggestion on how to create one, that would great--right now it's sort of an unappealing blend of stubs and ArgumentCaptors.

@TimvdLippe
Copy link
Contributor

Hm. I would have assumed that you can create the captor just like the others, with the @Captor annotation.

Given that the fix works, I am inclined to close this issue as WAI.

@DaveTryon
Copy link
Author

@TimvdLippe, the @captor worked--I had tried it but got hung up on the parameter matching. Fixed now, thanks!

Re: WAI, I couldn't find any indication of intentionally deprecating the old behavior, even if preferred options were available. Breaking behavior accidentally is generally considered a regression, but your team will ultimately make that call.

Thanks again for your help on this!

@TimvdLippe
Copy link
Contributor

We didn't intentionally break it, but I think this is a case of https://www.hyrumslaw.com/ Luckily we have alternatives in our public API that should suit your use case. Thanks for raising the issue!

DaveTryon added a commit to microsoft/accessibility-insights-for-android-service that referenced this issue Jan 26, 2023
#### Details

Due to mockito/mockito#2860, Dependbot has
been unable to update to any version of `mockito-inline` since version
4.6.1. The failed attempts are as follows:

Version | Failed PR
--- | ---
4.7.0 | #169 
4.8.0 | #174
4.8.1 | #176
4.9.0 | #182
4.11.0 | #198 

In response to mockito/mockito#2860, a
suggestion was made to use an `ArgumentCaptor` to capture the arguments
and the `RETURNS_SELF` parameter for the builder. This PR makes that
change, so we're now capturing arguments using the construct that is
intended for that purpose. The mockito team has not committed to fixing
this issue and may even be leaning toward not fixing it, since a more
officially supported option exists to do what we're doing.

I've applied these changes locally to #198 and confirmed that the tests
complete as expected.

##### Motivation

Allow newer versions of mockito

##### Context

<!-- Are there any parts that you've intentionally left out-of-scope for
a later PR to handle? -->

<!-- Were there any alternative approaches you considered? What
tradeoffs did you consider? -->

#### Pull request checklist
<!-- If a checklist item is not applicable to this change, write "n/a"
in the checkbox -->

- [n/a] Addresses an existing issue:
- [n/a] Added/updated relevant unit test(s)
- [x] Ran `./gradlew fastpass` from
`AccessibilityInsightsForAndroidService`
- [x] PR title _AND_ final merge commit title both start with a semantic
tag (`fix:`, `chore:`, `feat(feature-name):`, `refactor:`).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants