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

spy does not initialize objects fully with InstrumentationMemberAccessor #2947

Closed
aswath80 opened this issue Mar 25, 2023 · 7 comments
Closed
Assignees

Comments

@aswath80
Copy link

aswath80 commented Mar 25, 2023

With mockito-inline, spy objects are missing fields when spying on child classes whose base class constructor is private. The issue goes away when base class constructor is made default access or public.

public class MockitoInnerClassSpyTest {

    static class P1 {

        @Override
        public String toString() {
            return "P1";
        }
    }

    static class P2 {

        @Override
        public String toString() {
            return "P2";
        }
    }

    static class C {

        private final P1 p1;

        private C(final P1 p1) {
            this.p1 = p1;
        }

        public void print() {
            System.out.println("C: p1=" + p1);
        }

        private static class D
            extends C {

            private final P2 p2;

            D(final P1 p1, final P2 p2) {
                super(p1);
                this.p2 = p2;
            }

            @Override
            public void print() {
                super.print();
                System.out.println("D: p2=" + p2);
            }
        }
    }

    @Test
    void test() {
        final C.D d = new C.D(new P1(), new P2());
        d.print();
        final C.D spy = spy(d);
        System.out.println("------------ spy ---------------");
        spy.print();
    }
}

This prints

C: p1=P1
D: p2=P2
------------ spy ---------------
C: p1=P1
D: p2=null

p2 is non-null in original object but becomes null in the spy. This issue does not happen in mockito-core with ReflectionMemberAccessor. If the constructor of C is made default or public, the issue does not occur.

Versions:

Mockito-Inline : 4.10
JDK - 17

check that

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

raphw commented Mar 25, 2023

Ah, after a bit of head scratching I figured it out.

We have a snippet of code that selects the super constructor to call from a constructor that is

MethodList<MethodDescription.InDefinedShape> constructors =
    instrumentedType
        .getSuperClass()
        .asErasure()
        .getDeclaredMethods()
        .filter(isConstructor().and(not(isPrivate())));

That visibility check is no longer correct with nestmates (Java 11+) where javac does no longer create bridge methods to give access to private constructors, but where the access right is registered as an attribute. isPrivate needed to be replaced with isAccessible where the access right is evaluated with respect to all available properties by Byte Buddy.

@raphw
Copy link
Member

raphw commented Mar 25, 2023

Fixed in #2948

@aswath80
Copy link
Author

Thank you, that was quick!

@aswath80
Copy link
Author

I see the merge to master, is there a 4.x version where I can get this fix ?

@raphw
Copy link
Member

raphw commented Mar 25, 2023

Unfortunately no, we don't have capacity to backport changes.

@aswath80
Copy link
Author

I still need this to work on both Java 8 & 17 and so can't switch to 5.x. I guess the only option is to switch private access to default in the outer class until we can switch to 5.x.

@raphw
Copy link
Member

raphw commented Mar 25, 2023

If you make the constructor package-private, that's basically what javac did for you on 8.

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