Mocked but still null when using PowerMock to mock private method

54 Views Asked by At

Here are the brief codes of my project, class Foo:

public class Foo {
    @Autowired
    protected Mapper mapper;

    protected User getUser() {
        // ...
        return user;
    }
}

class Bar that extends Foo:

public class Bar extends Foo{

    public void func(String param1, String param2) {
        Result result = this.validateParams(param1, param2);
        // ...
        Entity entity = super.mapper.mapTo(param1, Entity.class);
        entity.setUserInfo(super.getUser().getInfo()); // null pointer exception
    }



    private Result validateParams(String param1, String param2) {
        // ...
        return Result.success();
    }
}

and the test class:

@RunWith(PowerMockRunner.class)
@PowerMockIgnore( {"javax.management.*", "javax.net.ssl.*"})
@PrepareForTest({Bar.class}) // to mock private method
public class BarTest {
    @InjectMocks
    Bar bar;

    @Mock
    Mapper mapper;

    @Test
    public void testFunc() {
        bar = PowerMockito.spy(bar);
        bar.mapper = mapper; // or bar.mapper is null

        PowerMockito.doReturn(Result.success()).when(bar, validateParams, any(), any()); // mock private method

        User user = new User(); // mock user
        PowerMockito.doReturn(user).when(bar).getUser(); // doesn't work

        bar.func("param1", "param2");
    }
}

It's weird that when I test bar.func, when it comes to entity.setUserInfo(super.getUser().getInfo()), super.getUser() return null, causing a NPE. But I've already mocked getUser() function, which seems to not work. Looking forward for your advice & help, thanks a lot.

I tried to avoid using spy(bar), assuming that it would cause a empty copy of instance(which is all null). Also I tried to use

Method methodGetUser = PowerMockito.method(Bar.class, "getUser");
    PowerMockito.replace(methodGetUser).with(new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return user;
        }
    });

but doesn't work as well(NPE).

1

There are 1 best solutions below

0
knittl On

@InjectMocks Bar bar; creates an instance already. bar = PowerMockito.spy(bar); assigns a new instance. The original bar instance does not know anything about the wrapping spy. So any behavior you define on the spy (with PowerMockito.doReturn(user).when(bar).getUser();) is not visible nor accessible from within your original instance.

You must keep the original reference around:

public class BarTest {
    @InjectMocks
    Bar bar;
    Bar barSpy;

    @Mock
    Mapper mapper;

    @Test
    public void testFunc() {
        barSpy = PowerMockito.spy(bar);
        // bar.mapper = mapper; // this should not be required, `@InjectMocks` must take care of this

        PowerMockito.doReturn(Result.success()).when(bar, validateParams, any(), any()); // mock private method

        User user = new User();
        PowerMockito.doReturn(user).when(bar).getUser(); // stub original instance

        bar.func("param1", "param2");
    }
}

But why are you spying? You are not verifying any calls on your spy?

Related: Why are my mocked methods not called when executing a unit test?