简体   繁体   中英

stubbing methods that manipulates parameters with mockito

I have the following situation:

class Worker {  
  public Integer somework() {  
      Integer k=0;  
      Helper h= new Helper();  
      h.change(k);  
      return k;  
    }
}

class Helper {
  public void change(Integer k) {
    //k = Some calcs
  }
}

I'm making unitests for Worker and obviously I want to mock Helper class so that his change method will always put 1 into k .

My real situation is more complicated but this code represents the issue. Thanks for help.

I have a method with definition like this:

class Template{
   public void process(Object rootMap, StringWriter out){
 .......   
  }
 }

I will show you how you can change/modify the "out"(StringWriter) reference.

private final Template mocktTemplate = mock(Template.class);
doAnswer(new Answer<StringWriter>() {

            public StringWriter answer(InvocationOnMock invocation)
                    throws Throwable {
                Object[] args = invocation.getArguments();
                if (args[1] instanceof StringWriter) {
                    StringWriter stringWriter = (StringWriter) args[1];
                    stringWriter.write("Email message");
                }
                return null;
            }
        }).when(this.mocktTemplate).process(anyObject(),any(StringWriter.class));

Now when you do make the actual call like:

msgBodyTemplate.process(model, msgBodyWriter);

the value of Stringbuffer ref in msgBodyWriter will be "Email message"; irrespective of it earlier value.

I think doAnswer is the best method dealing with void method where the method manipulates the given parameters.

doAnswer(new Answer() {
  public Object answer(InvocationOnMock invocation) {
      Object[] args = invocation.getArguments();
      Mock mock = invocation.getMock();
      return null;
  }})
.when(mock).someMethod();

Basically, after getting the arguments you can do whatever modification you want. There's a blog post explaining it a bit.

To @JB Nizet's point, yes, it's good to refactor for testability. Often refactoring to make code more testable leads to code that is better for other reasons - separation of concerns and such. It's not always possible to do. Say it's not your code, or you have some other requirement to leave it alone (because lots of other classes rely on it being the way it is) or whatever.

If I understand what you need to do, I think you can do it with a spy:

Worker workerUnderTest = new Worker();
Worker spiedWorkerUT = spy(workerUnderTest);
Helper mockHelper = mock(Helper.class);
when(spiedWorkerUT.createHelper()).thenReturn(mockHelper);
Integer actual = spiedWorkerUT.someWork();
verify(mockHelper).change(0);

Then use the spiedWorkerUT instead of the workerUnderTest to run your tests.

It's not always possible to avoid instantiating something you want to mock. For that, there is PowerMock .

Helper mockHelper = mock(Helper.class);
whenNew(Helper.class).withNoArguments().thenReturn(mockHelper);

I would change the method signature and make it take a Helper instance as argument. The caller would create the helper and pass it to the somework method. The test would pass a mock helper.

If this isn't possible, at least call a protected factory method to create the helper, and mock this factory method when testing the somework method in order to make it return a mock helper:

class Worker {  
    public Integer somework(){  
        Integer k=0;  
        Helper h= createHelper();  
        h.change(k);  
        return k;  
    }

    // this method may be mocked when testing somework, to return a mock helper.
    protected Helper createHelper() {
        return new Helper();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM