使用 Mockito 模拟局部范围对象的方法

发布于 2024-11-17 23:02:46 字数 198 浏览 8 评论 0原文

我需要一些帮助:

示例:

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

执手闯天涯 2024-11-24 23:02:46

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

这是一个基于此的解决方案。从问题中获取代码:

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

以下测试将通过准备实例化它的类来创建 MyObject 实例类的模拟(在本例中我将其称为 MyClass)使用 PowerMock 并让 PowerMockito 存根 MyObject 类的构造函数,然后让您存根 MyObject 实例 方法1()调用:

@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() 调用将返回您想要的内容。

如果您喜欢单行代码,您可以通过创建模拟和内联存根来缩短代码:

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

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();   
蹲在坟头点根烟 2024-11-24 23:02:46

如果您确实想避免接触此代码,可以使用 Powermockito (PowerMock for Mockito)。

有了这个,除其他外,您可以模拟新对象的构造简单的方法。

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.

如歌彻婉言 2024-11-24 23:02:46

决不。您将需要一些依赖项注入,即不应实例化 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
}

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
}
单调的奢华 2024-11-24 23:02:46

在最新的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();
}

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();
}
迷鸟归林 2024-11-24 23:02:46

您可以避免更改代码(尽管我推荐鲍里斯的答案)并模拟构造函数,就像在本示例中模拟方法内创建 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);
    }
}

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);
    }
}
背叛残局 2024-11-24 23:02:46

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

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();
}
森罗 2024-11-24 23:02:46

您可以通过在 MyObject 中创建工厂方法来实现此目的:

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

然后使用 PowerMock 模拟该方法。

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

总之,您可能不应该尝试这样做。相反,让测试驱动您的代码(又名 TDD),您将得到一个解决方案,例如:

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文