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

问题描述 投票:0回答:1

有没有什么办法,用 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"));
  }
}

The 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() 来返回一个部分嘲讽(spy)也是行不通的。Singleton 类没有提供一个无参数构造函数。

能否用其他方式实现想要的嘲讽?对于非单子类能否做到?

java mocking mockito powermockito intercept
1个回答
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]);
    }
}

第二,为什么不使用stub 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静态getInstance来返回mock。注意:你必须在得到真实实例后调用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]);
    }
}

© www.soinside.com 2019 - 2024. All rights reserved.