使用 Mockito 实现本地范围对象的 Mocking 方法

我需要帮助:

例如:

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

我想在我的测试中模仿 obj1.method1(),但是要做到透明,所以我不想修改代码。 在 Mockito 有什么办法吗?

148052 次浏览

不可能。你需要一些依赖注入,也就是说不需要实例化它,它应该由一些工厂提供。

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
}

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

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

然后用 PowerMock来嘲笑它。

但是,通过模仿局部范围对象的方法,您依赖于方法实现的那一部分保持不变。因此,您将失去重构方法的这一部分而不会中断测试的能力。此外,如果您在模拟中使用存根化返回值,那么您的单元测试可能会通过,但是在使用实际对象时,该方法可能会出现意外的行为。

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

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

传递依赖项,您可以很容易地为单元测试模拟这个依赖项。

如果您确实希望避免触及此代码,可以使用 强大的力量(PowerMock for Mockito)。

有了这个,在许多其他的事情中,你可以用一种非常简单的方式 模拟新对象的构造

您可以避免更改代码(尽管我建议使用 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);
}
}

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

下面是一个基于这个问题的解决方案:

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

下面的测试将通过准备实例化 MyObject实例类的类(在这个例子中我称之为 MyClass)和 PowerMock创建一个模拟 MyObject实例类,并让 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();

如果你不喜欢使用 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();
}

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

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