【问题标题】:Intercepting real non-static method calls with Mockito使用 Mockito 拦截真正的非静态方法调用
【发布时间】:2020-05-09 03:50:46
【问题描述】:

有什么方法可以使用MockitoPowerMockito 来拦截对对象的非静态方法的调用,或者至少是对单例对象的调用?

以下类提供了一个示例:

public class Singleton {

  private static Singleton INSTANCE = null;

  private Singleton(Object parameter) {}

  public static Singleton getInstance(Object parameter) {
    if (INSTANCE == null) {
      INSTANCE = new Singleton(parameter);
    }
    return INSTANCE;
  }

  public String process(String a, String b) {
    return (a + b);
  }

  // Other methods
}

public class Foreign {

  private Foreign() {}

  public static void main(String[] args) {
    System.out.println(Singleton.getInstance(new Object()).process("alpha", "beta"));
  }
}

Singleton 对象是在 Foreign 类中创建的,不受某些测试代码的控制(上面未显示)。这两个类都不能修改。目的是拦截测试代码中对非静态process() 方法的调用,以便对于某些值返回不同的结果,例如通话

Singleton.getInstance(new Object()).process("alpha", "beta");

模拟返回 "alpha-beta" 而不是预期的 "alphabeta"

一种解决方案可能是拦截 Singleton.getInstance() 方法来实例化 Singleton 的自定义子类,例如使用

public class SubSingleton extends Singleton {

  public SubSingleton(Object parameter) {
    super(parameter);
  }

  public String process(String a, String b) {
    if ("alpha".equals(a) && "beta".equals(b)) {
      return a + "-" + b;
    }
    return super.process(a + b);
  }
}

然后,对Singleton.process() 方法的调用将被拦截,如下所示:

Object parameter = new Object();
PowerMockito.doReturn(new SubSingleton(parameter)).when(Singleton.class, "getInstance", parameter);

但是,上面的Singleton类只提供了一个私有构造函数,所以不能扩展。使用PowerMockito.whenNew() 返回部分模拟(间谍)也将不起作用,因为Singleton 类不提供无参数构造函数。

可以通过任何其他方式实现所需的模拟吗?可以对非单例类进行吗?

【问题讨论】:

    标签: java mocking mockito powermockito intercept


    【解决方案1】:

    首先,您可以将 whenNew 用于带有一些参数的构造函数的对象:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(Singleton.class)
    public class SingletonPrivateNewTest {
    
        @Mock
        Singleton singletonMock;
    
        @Before
        public void setUp() throws Exception {
            PowerMockito.whenNew(Singleton.class)
                    .withAnyArguments()
                    .thenReturn(singletonMock);
        }
    
        @Test
        public void testMockNew() throws Exception {
            Mockito.when(singletonMock.process(anyString(), anyString())).thenReturn("sasa");
            Foreign.main(new String[0]);
        }
    }
    

    其次,为什么不存根 getInstance 而不是 new:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(Singleton.class)
    public class SingletonPrivateNewTest {
    
        @Test
        public void testMockNew() {
            PowerMockito.mockStatic(Singleton.class);
            Singleton singletonMock = Mockito.mock(Singleton.class);
            PowerMockito.when(Singleton.getInstance(any())).thenReturn(singletonMock);
            Mockito.when(singletonMock.process(anyString(), anyString())).thenReturn("sasa");
            Foreign.main(new String[0]);
        }
    }
    

    三、拦截过程方法:

    • 创建真正的单例
    • 创建一个模拟单例
    • mock static getInstance 返回模拟。注意:获取真实实例后必须调用 mockStatic。
    • 使用 thenAnswer 检查process 调用的参数
      • 如果它们匹配所需的模式,则返回所需的答案
      • 在真正的单例上调用真正的方法
    @RunWith(PowerMockRunner.class)
    @PrepareForTest(Singleton.class)
    public class SingletonPrivateNewTest {
    
        @Test
        public void testMockNew() {
            var singletonReal = Singleton.getInstance(new Object());
            var singletonMock = Mockito.mock(Singleton.class);
            PowerMockito.mockStatic(Singleton.class);
            PowerMockito.when(Singleton.getInstance(any())).thenReturn(singletonMock);
            Mockito.when(singletonMock.process(anyString(), anyString())).thenAnswer((args) -> {
                String a = args.getArgument(0);
                String b = args.getArgument(1);
                if ("alpha".equals(a) && "beta".equals(b)) {
                    return "sasa";
                } else {
                    return singletonReal.process(a, b);
                }
            });
            Foreign.main(new String[0]);
        }
    }
    

    最后,使用间谍而不是模拟

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(Singleton.class)
    public class SingletonPrivateNewTest {
    
        @Test
        public void testMockNew() {
            var singletonReal = Singleton.getInstance(new Object());
            var singletonMock = Mockito.spy(singletonReal);
            PowerMockito.mockStatic(Singleton.class);
            PowerMockito.when(Singleton.getInstance(any())).thenReturn(singletonMock);
            Mockito.when(singletonMock.process("alpha", "beta")).thenReturn("sasa");
            // NOTE: real method is called for other args
            Foreign.main(new String[0]);
        }
    }
    
    

    【讨论】:

    • 第一种方法没有提供解决方案,因为它拦截了mock对象的process(),而不是单例,这是Foreign和其他类实际使用的,调用其他方法Singleton 类的。这同样适用于第二种方法,因为 Singleton 类也有其他方法(在问题中澄清)。第三种方法应该适用于单例,因为间谍活动发生在真实对象上。适用于非单身人士吗? +1 并感谢您的回答。
    • 1.我的回答为您提供了解决方案的各个阶段。第一个显示当构造函数有参数时,您可以使用 whenNew,您认为这是不可能的。 2.如果有其他方法,你只想拦截一些调用,第3和第4个sn-p是要走的路(我更喜欢第4个)。 3. 我对Singleton.getInstance(Object parameter) 感到很困惑,如果单例已经存在,它会接受一个参数并忽略它。 4. 对非单例的适用性——我需要一个具体的例子。
    • 1.感谢您的回答,+1 并接受。我评论的意思是whenNew() 没有提供解决方案,因为它提供了一个模拟对象,而不是真正的单例。 2. 根据我之前的评论,4 日同意。 3.代码是一个简化的例子,参数可以是任何东西,也可以有多个。 4. 任何应用程序,只要存在多个实例,就被外部类使用。如果传递了模拟参数,则带参数的私有构造函数并不总是可用,因为对真实方法的调用可能不会产生有效的结果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多