【问题标题】:Mocking methods of local scope objects with Mockito使用 Mockito 模拟本地范围对象的方法
【发布时间】:2011-09-25 02:27:22
【问题描述】:

我需要一些帮助:

例子:

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

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

【问题讨论】:

    标签: object mocking local mockito


    【解决方案1】:

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

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

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

    以下测试将创建一个模拟 MyObject 实例类,方法是使用 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() 调用将返回您想要的。

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

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

    【讨论】:

    • 如果我使用 EclEmma 进行代码覆盖,这种方法的问题是,如果我将 MyClass.class 添加到 @PrepareForTest 而不是实际的代码覆盖率,EclEmma 会为 MyClass 提供 0% 的代码覆盖率。我认为这是 EclEmma 工具的限制或错误。有什么办法可以解决这个问题吗?
    • @Vidyasagar 我在 Sonar/Jacoco 中发现了同样的问题。
    【解决方案2】:

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

    有了这个,除其他外,您可以以非常简单的方式mock the construction of new objects

    【讨论】:

    • 注意了,但为了将来使用,我将使用 PowerMock
    • @edutesoy 你能写完整的代码吗?我无法理解你的想法。我没有看到构造函数模拟和局部变量模拟之间的关系。
    • 如果我使用 EclEmma 进行代码覆盖,这种方法的问题是,如果我将 MyClass.class 添加到 @PrepareForTest 而不是实际的代码覆盖率,EclEmma 会为 MyClass 提供 0% 的代码覆盖率。我认为这是 EclEmma 工具的限制或错误。有什么办法可以解决这个问题吗?
    【解决方案3】:

    没办法。您将需要一些依赖注入,即不应将 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
    }
    

    【讨论】:

    • 这是我的想法,但这会添加更多不必要的代码,因为我需要从本地范围对象模拟一个方法。
    • 我不想创建 Factory 来创建 myObject 的新实例,并且该代码是不必要的,因为它仅用于模拟局部范围变量的方法。
    • 您必须有一个工厂才能在每次调用 method1 时拥有一个新的 MyObject 实例
    • @Boris Pavlović 嗨,鲍里斯,我发现您的回答非常有趣,我正在尝试将其应用于我的一个与此问题非常相似的示例。但我无法使测试通过。你能看看:stackoverflow.com/questions/15350239/…如果你能给我一些提示,我做错了什么?
    • @sfrj 看来 tallseth 已经正确回答了 (stackoverflow.com/a/15352255/32090)
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      您可以通过在 MyObject 中创建工厂方法来做到这一点:

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

      然后用 PowerMock 模拟它。

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

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

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

      传入依赖项,您可以轻松地对其进行模拟以进行单元测试。

      【讨论】:

      • 我会选择这个解决方案。由于在 @PrepareforTest 中添加测试类,结果导致 jacoco 或 eclemma 没有覆盖,这是 powemock 的一个问题。很高兴我看到了这个!谢谢! ?
      【解决方案6】:

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

      【讨论】:

        猜你喜欢
        • 2015-06-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-08
        • 1970-01-01
        • 2016-04-07
        相关资源
        最近更新 更多