Mocking Reflection based calls
Asked Answered
B

2

15

I am trying to mock some reflection based methods. Below you can see the details,

Class Under Test

public class TracerLog {
    @AroundInvoke
    public Object logCall(InvocationContext context) throws Exception {
        Logger logger = new Logger();
        String message = "INFO: Invoking method - " 
                + context.getMethod().getName() + "() of Class - " 
                + context.getMethod().getDeclaringClass();

        logger.write(message);
        return context.proceed();
    }
}

Test

public class TracerLogTest {

@Mock
InvocationContext mockContext;
@Mock
Logger mockLogger;
@InjectMocks
private TracerLog cut = new TracerLog();

@BeforeMethod
public void setup() {
    MockitoAnnotations.initMocks(this);
}

@Test
public void logCallTest() throws Exception {
    when(mockContext.proceed()).thenReturn(true);
    when(mockContext.getMethod().getDeclaringClass().getName()).thenReturn("someClass");
    cut.logCall(mockContext);
    verify(mockContext).proceed();
}

}

or

@Test
public void logCallTest() throws Exception {
    when(mockContext.proceed()).thenReturn(true);
    when(mockContext.getMethod().getName()).thenReturn("someMethod");
    when(mockContext.getMethod().getDeclaringClass().getName()).thenReturn("someClass");
    cut.logCall(mockContext);
    verify(mockLogger).write(anyString());
    verify(mockContext).proceed();
}

But, the tests fail with a NullPointerException. I understand that I am doing something wrong against mocking concepts, but I do not understand what it is. Could you please throw some light on it and also suggest me how this method can be tested?

Thanks.

Breviary answered 25/1, 2012 at 22:10 Comment(2)
How are you creating your mock objects and your object under test? Can you post your entire test class, rather than just the test method? Thanks.Grog
@DavidWallace I have now edited the question to include the complete code.Breviary
G
28

You need a Method object and a Class object. As per your comment, Mockito cannot mock a Method, so you'll need a real one. I haven't tested this, but I believe this would work. Instead of:

when(mockContext.getMethod().getName()).thenReturn("someMethod");
when(mockContext.getMethod().getDeclaringClass().getName()).thenReturn("someClass");

You need:

// any method will do, but here is an example of how to get one.
Method testMethod = this.getClass().getMethod("logCallTest");

when(mockContext.getMethod()).thenReturn(testMethod);

Obviously, getName() will not return "someMethod" anymore and getDeclaringClass().getName() will return the name of this test class (in the example), but although you couldn't pick what they return, what they return is still deterministic, so you should be able to verify anything you need. (Of course, if you needed to spy or verify that a call was made on the Method object itself, you're still stuck.)

Gettings answered 25/1, 2012 at 22:57 Comment(4)
Thanks for your answer, but Mockito complains that it cannot mock or spy java.lang.reflect.Method. Are there any other ways to achieve this?Breviary
I've edited my answer to show how to use a real Method object. This should address the simple examples in your question, but there are still limitations.Gettings
Thanks for your suggestion, but mockContext.getMethod().getDeclaringClass() will not work in this case.Breviary
Why not? A real method does have a declaring class. So you will get the real declaring class.Gettings
G
4

Yeah the problem is that mockContext.getMethod() is going to return null. So every time you run this, then call something on the result (getDeclaringClass() or getName()) you'll get the NPE. You probably want to use the RETURNS_DEEP_STUBS default answer, when you set up the mock. Something like

@Mock( answer = RETURNS_DEEP_STUBS ) private InvocationContext mockContext; 

should do the trick.

Grog answered 26/1, 2012 at 10:38 Comment(8)
Hi David I mostly agree with you. But for the reader : the Method is a final class, it can't be mocked with Mockito ;) Also I wouldn't recommend to mock types you don't own.Weasner
Oh bother. Brice is right (he usually is, about these things). Since Mockito can't mock final classes, I don't see a way to do what the original poster wanted. Sorry.Grog
@Brice Well, I agree with you, but InvocationContext is not a final class and it has to be mocked in my case because, I do not want the application server to be running for my test. Did I go wrong somewhere?Breviary
@Breviary I was only talking about the Method class, you should go with the answer of jhericks there https://mcmap.net/q/765676/-mocking-reflection-based-calls David, I can miss a things too ;)Weasner
@Brice Ofcourse on the other hand java.lang.reflect.Method cannot be mocked using Mockito. This is where I need some help. Can you please give me some ideas how to handle this?Breviary
@Breviary Yeah Method is an object created by the JVM internals. The only way I can think of is to write something like that that Method testMethod = YourOwnClass.class.getDeclaredMethod("someMethod"); when(invocationContext.getMethod()).thenReturn(testMethod);. It would be even better to actually create your own instance of InvocationContext, instead of mocking it (i.e. don't mock types you don't own). I hope that helps.Weasner
@Brice Your first suggestion may not work for mockContext.getMethod().getDeclaringClass(). Reg. creating my own instance of InvocationContext - This object is created by the EJB container and I cannot create it in the unit testing environment without starting the container and I do not want the role of container in my unit test :-(Breviary
@Breviary Oh yeah, I forgot you worked with EJBs. Then I suppose you are almost ok to mock InvocationContext (But avoid that as much as possible see the 4-5 first google results google.fr/…). Maybe there's some project that aim to provide JEE unit test support (not the Arquillian way which actually deploy EJBs).Weasner

© 2022 - 2024 — McMap. All rights reserved.