使用Mockito模拟局部作用域对象的方法



我需要一些帮助:

示例:

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

我想在测试中模拟obj1.method1(),但要透明,所以我不想制作和更改代码。在Mockito有什么办法做到这一点吗?

@edutesoy的答案指向PowerMockito的文档,并提到构造函数mocking作为提示,但没有提到如何将其应用于问题中的当前问题。

以下是基于此的解决方案。从问题中提取代码:

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

以下测试将创建MyObject实例类的mock,方法是准备用PowerMock实例化它的类(在本例中,我称之为MyClass),并让PowerMockito存根MyObject类的构造函数,然后让您存根MyObject实例method1()调用:

@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 
    }
}

这样,您的内部method1()呼叫将返回您想要的内容。

如果你喜欢一行代码,你可以通过内联创建mock和stub来缩短代码:

MyObject myObjectMock = when(mock(MyObject.class).method1()).thenReturn(<whatever you want>).getMock();   

如果你真的想避免接触这个代码,你可以使用Powermockito(PowerMock for Mockito)。

有了这一点,除其他许多事情外,您可以用一种非常简单的方式模拟新对象的构造。

不可能。您将需要一些依赖项注入,也就是说,与其实例化obj1,不如由某个工厂提供。

MyObjectFactory factory;
public void setMyObjectFactory(MyObjectFactory factory)
{
  this.factory = factory;
}
void method1()
{
  MyObject obj1 = factory.get();
  obj1.method();
}

然后你的测试会看起来像:

@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
}

在最新的mockito版本和junit5中,如果没有PowerMock,新实例创建和静态方法的模拟都是可能的。

查看方法Mockito.mockConstruction()Mockito.mockStatic()

在您的情况下:

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();
}

您可以避免更改代码(尽管我建议Boris给出答案)并模拟构造函数,就像本例中模拟在方法中创建File对象一样不要忘记将要创建文件的类放在@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);
    }
}

如果您不喜欢使用PowerMock,您可以尝试以下方法:

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

这样写你的测试:

@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();
}

您可以通过在MyObject:中创建一个工厂方法来实现这一点

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

然后用PowerMock来嘲笑它。

然而,通过模拟本地作用域对象的方法,您将依赖于方法实现的这一部分保持不变。因此,您失去了在不破坏测试的情况下重构该部分方法的能力。此外,如果您在mock中存根返回值,那么您的单元测试可能会通过,但该方法在使用真实对象时可能会出现意外行为。

总之,你可能不应该尝试这样做。相反,让测试驱动你的代码(也称为TDD),你会得到这样的解决方案:

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

传入依赖项,您可以轻松地为单元测试模拟该依赖项。

相关内容

  • 没有找到相关文章

最新更新