Mocking methods of local scope objects with Mockito
Asked Answered
K

7

81

I need some help with this:

Example:

void method1{
    MyObject obj1=new MyObject();
    obj1.method1();
}

I want to mock obj1.method1() in my test but to be transparent so I don't want make and change of code. Is there any way to do this in Mockito?

Kunming answered 29/6, 2011 at 11:56 Comment(0)
S
74

The answer from @edutesoy points to the documentation of PowerMockito and mentions constructor mocking as a hint but doesn't mention how to apply that to the current problem in the question.

Here is a solution based on that. Taking the code from the question:

public class MyClass {
    void method1 {
        MyObject obj1 = new MyObject();
        obj1.method1();
    }
}

The following test will create a mock of the MyObject instance class via preparing the class that instantiates it (in this example I am calling it MyClass) with PowerMock and letting PowerMockito to stub the constructor of MyObject class, then letting you stub the MyObject instance method1() call:

@RunWith(PowerMockRunner.class)
@PrepareForTest(MyClass.class)
public class MyClassTest {
    @Test
    public void testMethod1() {      
        MyObject myObjectMock = mock(MyObject.class);
        when(myObjectMock.method1()).thenReturn(<whatever you want to return>);   
        PowerMockito.whenNew(MyObject.class).withNoArguments().thenReturn(myObjectMock);
        
        MyClass objectTested = new MyClass();
        objectTested.method1();
        
        ... // your assertions or verification here 
    }
}

With that your internal method1() call will return what you want.

If you like the one-liners you can make the code shorter by creating the mock and the stub inline:

MyObject myObjectMock = when(mock(MyObject.class).method1()).thenReturn(<whatever you want>).getMock();   
Stollings answered 4/6, 2015 at 0:42 Comment(2)
Problem with this approach if i use EclEmma for code coverage is that EclEmma gives 0% code coverage for MyClass if i add MyClass.class into @PrepareForTest instead of the actual code coverage. I think this is a limitation or bug with EclEmma tool. Any idea to overcome this issue?Philan
@Vidyasagar I Found the same Issue in Sonar/Jacoco.Apart
E
34

If you really want to avoid touching this code, you can use Powermockito (PowerMock for Mockito).

With this, amongst many other things, you can mock the construction of new objects in a very easy way.

Emmerich answered 30/6, 2011 at 9:23 Comment(3)
Note taken, but for future use I will use PowerMockKunming
@edutesoy Can you write full code? I cannot understand your idea. I don't see relation between constructor mocking and local variabli mocking.Adrianople
Problem with this approach if i use EclEmma for code coverage is that EclEmma gives 0% code coverage for MyClass if i add MyClass.class into @PrepareForTest instead of the actual code coverage. I think this is a limitation or bug with EclEmma tool. Any idea to overcome this issue?Philan
S
20

No way. You'll need some dependency injection, i.e. instead of having the obj1 instantiated it should be provided by some factory.

MyObjectFactory factory;

public void setMyObjectFactory(MyObjectFactory factory)
{
  this.factory = factory;
}

void method1()
{
  MyObject obj1 = factory.get();
  obj1.method();
}

Then your test would look like:

@Test
public void testMethod1() throws Exception
{
  MyObjectFactory factory = Mockito.mock(MyObjectFactory.class);
  MyObject obj1 = Mockito.mock(MyObject.class);
  Mockito.when(factory.get()).thenReturn(obj1);
  
  // mock the method()
  Mockito.when(obj1.method()).thenReturn(Boolean.FALSE);

  SomeObject someObject = new SomeObject();
  someObject.setMyObjectFactory(factory);
  someObject.method1();

  // do some assertions
}
Suttles answered 29/6, 2011 at 12:3 Comment(5)
This is what I have in mind, but this is adding more unnecessary code because I need to mock a method from local scope object.Kunming
I don't want to create Factory in order to create new instances of myObject and that code is unnecessary because it's only needed in order to mock method of local scope variable.Kunming
You got to have a factory in order to have a fresh instance of MyObject whenever method1 is invokedDarwindarwinian
@Boris Pavlović Hi Boris, I find your answer very interesting,And I am trying to apply it to one of my examples which is very similar to this issue. But I cant manage to make the test pass. Could you have a look at: #15350739 I appreciate if you could give me some tip, on what I am doing wrong?Septillion
@sfrj it seems that tallseth has already answered correctly (https://mcmap.net/q/76170/-breaking-a-local-dependency-to-unit-test-a-void-method)Darwindarwinian
T
12

Both mocking of a new instance creation and static methods is possible without PowerMock in the latest mockito versions and junit5.

Take a look in the methods Mockito.mockConstruction() and Mockito.mockStatic().

In your case:

try (MockedConstruction<MyObject> myobjectMockedConstruction = Mockito.mockConstruction(MyObject.class,
        (mock, context) -> {
            given(mock.method1()).willReturn("some result"); //any additional mocking
        })) {
    underTest.method1();
    assertThat(myobjectMockedConstruction.constructed()).hasSize(1);
    MyObject mock = myobjectMockedConstruction.constructed().get(0);
    verify(mock).method1();
}
Tupper answered 12/6, 2022 at 21:13 Comment(1)
Worked like a charm. Great update provided by Mockito.Purvey
G
3

You could avoid changing the code (although I recommend Boris' answer) and mock the constructor, like in this example for mocking the creation of a File object inside a method. Don't forget to put the class that will create the file in the @PrepareForTest.

package hello.easymock.constructor;

import java.io.File;

import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
    
@RunWith(PowerMockRunner.class)
@PrepareForTest({File.class})
public class ConstructorExampleTest {
        
    @Test
    public void testMockFile() throws Exception {

        // first, create a mock for File
        final File fileMock = EasyMock.createMock(File.class);
        EasyMock.expect(fileMock.getAbsolutePath()).andReturn("/my/fake/file/path");
        EasyMock.replay(fileMock);

        // then return the mocked object if the constructor is invoked
        Class<?>[] parameterTypes = new Class[] { String.class };
        PowerMock.expectNew(File.class, parameterTypes , EasyMock.isA(String.class)).andReturn(fileMock);
        PowerMock.replay(File.class); 
    
        // try constructing a real File and check if the mock kicked in
        final String mockedFilePath = new File("/real/path/for/file").getAbsolutePath();
        Assert.assertEquals("/my/fake/file/path", mockedFilePath);
    }
}
Gossoon answered 20/4, 2013 at 9:19 Comment(0)
P
3

If you don't prefer to use PowerMock, you may try the below way:

public class Example{
...
void method1(){
    MyObject obj1 = getMyObject();
    obj1.doSomething();
}

protected MyObject getMyObject(){
    return new MyObject();
}
...
}

Write your test like this:

@Mock
MyObject mockMyObject;

@Test
void testMethod1(){
    Example spyExample = spy(new Example());
    when(spyExample.getMyObject()).thenReturn(mockMyObject);
    //stub if required
    doNothing().when(mockMyObject.doSomething());
    verify(mockMyObject).doSomething();
}
Pustulant answered 21/4, 2021 at 14:55 Comment(0)
B
1

You can do this by creating a factory method in MyObject:

class MyObject {
    public static MyObject create() {
      return new MyObject();
    }
}

then mock that with PowerMock.

However, by mocking the methods of a local scope object, you are depending on that part of the implementation of the method staying the same. So you lose the ability to refactor that part of the method without breaking the test. In addition, if you are stubbing return values in the mock, then your unit test may pass, but the method may behave unexpectedly when using the real object.

In sum, you should probably not try to do this. Rather, letting the test drive your code (aka TDD), you would arrive at a solution like:

void method1(MyObject obj1) {
   obj1.method1();
}

passing in the dependency, which you can easily mock for the unit test.

Britanybritches answered 29/6, 2011 at 14:32 Comment(1)
I would go for this solution. As adding the tested class in @PrepareforTest results to no coverage in jacoco or eclemma which has been an issue with powemock. Glad I had saw this! Thank you! 😊Marianmariana

© 2022 - 2024 — McMap. All rights reserved.