Java单元测试实践-00.目录(9万多字文档+700多测试示例)
https://blog.csdn.net/a82514921/article/details/107969340

1. Mock/Spy后Stub Spring成员变量中的方法

在被测试代码中,Spring的@Component组件实现类中通常会依赖其他的成员变量,可能存在多层的依赖关系。为了使被测试代码能够完成足够完备的测试,覆盖足够的场景与代码分支,需要对被测试代码进行细粒度的控制,可能需要对成员变量的方法进行Stub。

例如被测试代码中调用了A类的对象a1的fa1()方法,在a1.fa1()方法中,调用了B类的对象b1的fb1()方法,为了使a1.fa1()方法中调用的b1.fb1()方法的行为被Stub,可将a1对象中注入的b1对象替换为B类的Mock或Spy对象,并对其fb1()方法进行Stub。通过以上操作后,在调用a1.fa1()方法时,会调用B类的Mock/Spy对象被Stub的方法,可以按照需求改变其行为。

示意图如下所示:

1.1. 使用Mock对象对成员变量进行替换

1.1.1. 替换成员变量为Mock对象

使用Whitebox.setInternalState()方法可以通过反射对私有的成员变量进行替换,可将Spring的@Component组件实现类中的成员变量替换为Mock对象,可对Mock对象的方法进行Stub,在被测试类调用成员变量的方法时按照需要改变行为。

示例如下:

被测试类为TestServiceB1Impl,在其中引用了TestServiceA1Impl类的实例,TestServiceB1Impl类的test1方法调用了TestServiceA1Impl类的test1方法。

生成TestServiceA1接口的Mock对象,对其test1方法进行Stub,并将TestServiceB1Impl实例中的TestServiceA1Impl类的实例替换为Mock对象。

当调用TestServiceB1Impl类的test1方法时,会调用TestServiceA1Impl类的Mock对象被Stub的test1方法。可参考示例TestSpMockMember类。

@Autowired
private TestServiceB1 testServiceB1;
TestServiceA1 testServiceA1 = Mockito.mock(TestServiceA1.class);
Mockito.when(testServiceA1.test1(Mockito.anyString())).thenReturn(TestConstants.MOCKED);
Whitebox.setInternalState(testServiceB1, testServiceA1);

1.1.2. 替换成员变量的成员变量为Mock对象

有时需要将被测试类的成员变量的成员变量替换为Mock对象,可以使用反射获取被测试类的成员变量( 可使用Whitebox.getInternalState()方法 ),再将成员变量的成员变量替换为Mock对象,并对其方法进行Stub。

示例如下:

被测试类为TestServiceC1Impl,在其中引用了TestServiceB1Impl类的实例,TestServiceB1Impl类中引用了TestServiceA1Impl类的实例。

TestServiceC1Impl类的test1()方法调用了TestServiceB1Impl类的test1()方法,在其中调用了TestServiceA1Impl类的test1()方法。

生成TestServiceA1接口的Mock对象,对其test1()方法进行Stub。通过反射获取TestServiceC1Impl类实例中的TestServiceB1Impl类的实例,将其中的TestServiceA1Impl类的实例替换为Mock对象。

当调用TestServiceC1Impl类的test1()方法时,会调用TestServiceB1Impl类的test1()方法,再调用TestServiceA1Impl类的Mock对象被Stub的test1()方法。可参考示例TestSpMockMemberOfM1类。

@Autowired
private TestServiceC1 testServiceC1;
TestServiceA1 testServiceA1 = Mockito.mock(TestServiceA1.class);
Mockito.when(testServiceA1.test1(Mockito.anyString())).thenReturn(TestConstants.MOCKED);
TestServiceB1 testServiceB1 = Whitebox.getInternalState(testServiceC1, TestServiceB1.class);
Whitebox.setInternalState(testServiceB1, testServiceA1);

1.1.3. Spring Bean单例与变量替换

由于Spring的Bean默认为单例的,将某个@Component组件对象中的成员变量替换为Mock/Spy对象后,所有注入该对象的其他原始对象均会受影响。

示意图如下所示:

示例如下:

测试代码中引用了TestServiceC1Impl与TestServiceC2Impl类的实例,以上两个类均引用了TestServiceB1Impl类的实例,TestServiceB1Impl类中引用了TestServiceA1Impl类的实例。

TestServiceC1Impl与TestServiceC2Impl类的test1()方法调用了TestServiceB1Impl类的test1()方法,在其中调用了TestServiceA1Impl类的test1()方法。

生成TestServiceA1接口的Mock对象,对其test1()方法进行Stub。通过反射获取TestServiceC1Impl类实例中的TestServiceB1Impl类的实例,将其中的TestServiceA1Impl类的实例替换为Mock对象。

当调用TestServiceC1Impl或TestServiceC2Impl类的test1()方法时,会调用TestServiceB1Impl类的test1()方法,再调用TestServiceA1Impl类的Mock对象被Stub的test1()方法,因此TestServiceC1Impl与TestServiceC2Impl类的test1()方法的行为均被改变了。可参考示例TestSpMockMemberOfM2类。

@Autowired
private TestServiceC1 testServiceC1;
@Autowired
private TestServiceC2 testServiceC2;
TestServiceA1 testServiceA1 = Mockito.mock(TestServiceA1.class);
Mockito.when(testServiceA1.test1(Mockito.anyString())).thenReturn(TestConstants.MOCKED);
TestServiceB1 testServiceB1InC1 = Whitebox.getInternalState(testServiceC1, TestServiceB1.class);
Whitebox.setInternalState(testServiceB1InC1, testServiceA1);

1.1.4. 将多个类引用的实例替换为独立的Mock对象

有时可能出现多个不同的类引用了同一个类的实例,可能需要将不同类中的被引用类的实例替换为独立的Mock对象,使不同的类在调用被引用类时能够按照需要执行不同的操作。

示例如下:

测试代码中引用了TestServiceC1Impl与TestServiceC2Impl类的实例,以上两个类均引用了TestServiceB1Impl类的实例。

TestServiceC1Impl与TestServiceC2Impl类test1()方法均调用了TestServiceB1Impl类的test1()方法。

可以将TestServiceC1Impl与TestServiceC2Impl类的实例中的TestServiceB1Impl类的实例分别替换为独立的Mock对象,使以上两个类在调用TestServiceB1Impl类的test1()方法时能够按照需要分别进行Stub。可参考示例TestSpMockMemberOfM3类。

@Autowired
private TestServiceC1 testServiceC1;
@Autowired
private TestServiceC2 testServiceC2;
TestServiceB1 testServiceB1InC1 = Mockito.mock(TestServiceB1.class);
Mockito.when(testServiceB1InC1.test1(Mockito.anyString())).thenReturn(TestConstants.FLAG1);
Whitebox.setInternalState(testServiceC1, testServiceB1InC1);
TestServiceB1 testServiceB1InC2 = Mockito.mock(TestServiceB1.class);
Mockito.when(testServiceB1InC2.test1(Mockito.anyString())).thenReturn(TestConstants.FLAG2);
Whitebox.setInternalState(testServiceC2, testServiceB1InC2);

1.1.5. 替换成员变量时防止覆盖Stub操作

在将成员变量替换为Mock对象之前,需要先判断该成员变量目前是否已经是Mock对象,若已经是Mock对象,需要直接对该Mock对象进行Stub;若每次都生成新的Mock对象进行替换,会将之前的Mock对象的Stub操作覆盖。可以参考示例代码TestReplaceUtil类的replaceMockMember()/replaceSpyMember(),将指定对象中的指定类型成员变量替换为Mock/Spy对象,且可以防止之前设置的覆盖Stub操作。

被测试类为TestServiceB1Impl,在其中引用了TestServiceA1Impl类的实例,test1()与test3()方法分别调用TestServiceA1Impl类的同名方法。

在对TestServiceB1Impl类实例中的TestServiceA1Impl类实例的test1()方法进行Stub时,生成了TestServiceA1接口的Mock对象,对其test1()方法进行Stub,再将TestServiceB1Impl类实例中的TestServiceA1Impl类实例替换为Mock对象。

在对TestServiceB1Impl类实例中的TestServiceA1Impl类实例的test3()方法进行Stub时,生成了TestServiceA1接口的Mock对象,对其test3()方法进行Stub,再将TestServiceB1Impl类实例中的TestServiceA1Impl类实例替换为Mock对象。

以上操作导致TestServiceB1Impl类实例中的TestServiceA1Impl类实例的Mock对象被替换为最后一次生成的Mock对象,仅包含对test3()方法的Stub,对test1()方法的Stub已被覆盖。可参考示例TestSpMockMemberOfM4Run、TestSpMockMemberOfM4Mock类。

@Autowired
private TestServiceB1 testServiceB1;
TestServiceA1 testServiceA1Mock = Mockito.mock(TestServiceA1.class);
Mockito.when(testServiceA1Mock.test1(Mockito.anyString())).thenReturn(TestConstants.MOCKED);
Whitebox.setInternalState(testServiceB1, testServiceA1Mock);
TestServiceA1 testServiceA1Mock2 = Mockito.mock(TestServiceA1.class);
Mockito.when(testServiceA1Mock2.test3(Mockito.anyString())).thenReturn(TestConstants.MOCKED);
Whitebox.setInternalState(testServiceB1, testServiceA1Mock2);

在对TestServiceB1Impl类实例中的TestServiceA1Impl类实例的test1()方法进行Stub时,判断TestServiceB1Impl类实例中的TestServiceA1Impl类实例是否为Mock对象,若非Mock对象,则生成Mock对象,对其test1()方法进行Stub后,再将TestServiceB1Impl类实例中的TestServiceA1Impl类实例替换为Mock对象;若为Mock对象,则直接对Mock对象进行Stub。

在对TestServiceB1Impl类实例中的TestServiceA1Impl类实例的test3()方法进行Stub时,判断TestServiceB1Impl类实例中的TestServiceA1Impl类实例是否为Mock对象,若非Mock对象,则生成Mock对象,对其test3()方法进行Stub后,再将TestServiceB1Impl类实例中的TestServiceA1Impl类实例替换为Mock对象;若为Mock对象,则直接对Mock对象进行Stub。

以上操作使TestServiceB1Impl类实例中的TestServiceA1Impl类实例的Mock对象仅生成一次,包含了每次的Stub操作,不会有Stub操作被覆盖。可参考示例TestSpMockMemberOfM5Run、TestSpMockMemberOfM5Mock方法。

public static void mock2(TestServiceB1 testServiceB1) {
    TestServiceA1 testServiceA1InB1 = Whitebox.getInternalState(testServiceB1, TestServiceA1.class);
    if (testServiceA1InB1.getClass() == TestServiceA1Impl.class) {
        TestServiceA1 testServiceA1Mock = Mockito.mock(TestServiceA1.class);
        doMock2(testServiceA1Mock);
        Whitebox.setInternalState(testServiceB1, testServiceA1Mock);
    } else {
        doMock2(testServiceA1InB1);
private static void doMock2(TestServiceA1 testServiceA1) {
    Mockito.when(testServiceA1.test3(Mockito.anyString())).thenReturn(TestConstants.MOCKED);

1.1.6. 变量替换与Stub的顺序

将变量替换为Mock对象,与对Mock对象的方法进行Stub的操作,以上两个操作的先后顺序不影响执行结果,可以任意指定。可参考示例TestSpMockMemberOrder类。

1.2. 使用Spy对象对成员变量进行替换

使用Spy对象对成员变量进行替换,与使用Mock对象对成员变量进行替换类似。

  • 替换成员变量为Spy对象

可参考示例TestSpSpyMember类。

  • 替换成员变量的成员变量为Spy对象

可参考示例TestSpSpyMemberOfM1类。

  • Spring Bean单例与变量替换

可参考示例TestSpSpyMemberOfM2类。

  • 将多个类引用的实例替换为独立的Spy对象

可参考示例TestSpSpyMemberOfM3类。

  • 替换成员变量时防止覆盖Stub操作

反例可参考示例TestSpSpyMemberOfM4Run、TestSpSpyMemberOfM4Spy类,正例可参考TestSpSpyMemberOfM5Run、TestSpSpyMemberOfM5Spy类。

  • 变量替换与Stub的顺序

可参考示例TestSpSpyMemberOrder类。

利用Mockito.spy Mockito提供一个spy功能,用于拦截那些尚未实现或不期望被真实调用的方法,默认所有方法都是真实方法,除非主动去模拟对应方法。所以,利用spy功能来定义被测对象,适合于需要模拟被测类自身方法的情况,适用于普通类、接口和虚基类。 UserService userService = Mockito.spy(new User Mock文的意思就是模拟,Mockito是一个模拟对象框架,主要还是用于单元测试,它通过隐藏真实的实现来返回我们期望的结果,这样我们就可以排除其它可能对当前单元的执行结果产生影响的其它因素,如我们的某个功能需要调用一个远程接口,但是这个时候我们只需要关心当前功能是否能够正常工作而不需要关心远程接口是否正常工作,我们就可以通过模拟远程接口返回的正确或者错误值进行测试。 这是两篇关于Moc 当使用PowerMockito.whenNew方法时,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是需要mock的new对象代码所在的类。 当需要mock final方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是final方法所在的类。  当需要moc MockitoAnnotations.initMocks(this),其this就是单元测试所在的类,在initMocks函数Mockito会根据类不同的注解(如@Mock, @Spy等)创建不同的mock对象,即初始化工作. @Before public void setUp() throws Exception { initMocks(this); 要测试的类.某个成员变量=xx } 如果是private成员变量,就要通过反射来设置。