使用 Mockito 拦截真正的非静态方法调用

Intercepting real non-static method calls with Mockito

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

以下 classes 提供了示例:

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 class 中创建的,不受某些测试代码(上面未显示)的控制。这两个class都不能修改。 objective 是拦截对测试代码中非静态 process() 方法的调用,以便对于某些值,不同的结果是 returned,例如通话

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

模拟为 return "alpha-beta" 而不是预期的 "alphabeta".

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

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

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

能否以任何其他方式实现所需的模拟?非单例可以吗classes?

首先,你可以对带有一些参数的构造函数的对象使用 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]);
    }
}

其次,为什么不用 stub 而不是 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 到 return 模拟。注意:您必须在获取真实实例后调用 mockStatic。
  • 使用 thenAnswer 检查 process 调用的参数
    • return 如果匹配所需的模式,则需要答案
    • 否则在真正的单例上调用真正的方法
@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]);
    }
}