Java单元测试实践-10.Mock非静态方法

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

1. Mock非静态方法

对非静态方法的进行Mock等处理时,部分情况与对Spring的@Component组件的处理类似,主要在后续Spring相关的内容进行详细说明,本部分的内容相对简略。

1.1. Mock后Stub非静态方法

1.1.1. 生成非静态方法对应的类的Mock对象

参考 https://github.com/mockito/mockito/wiki 。使用mock()方法可以通过代码创建Mock对象。

参考 https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/Mockito.html#mock-java.lang.Class- 。使用Mockito.mock()方法可以创建指定的类或接口的Mock对象。

对非静态方法进行Stub时,可以使用Mockito.mock()方法创建Mock对象,再对Mock对象的方法进行Stub。

1.1.2. Stub单例模式类的非静态方法

对于单例模式类的非静态方法进行Stub,可以按照以下步骤执行:

  • 对于使用单例模式的类,使用@PrepareForTest注解及PowerMockito.mockStatic()方法进行处理;

  • 通过Mockito.mock()方法创建对应类的Mock对象;

  • 对获取实例的静态方法进行Stub,将返回修改为Mock对象;

  • 针对需要Stub的非静态方法,对Mock对象进行Stub。

示例如下,可参考示例TestNStMSingleton1类。

PowerMockito.mockStatic(TestNonStaticSingleton1.class);

TestNonStaticSingleton1 testNonStaticSingleton1Mock = Mockito.mock(TestNonStaticSingleton1.class);

Mockito.when(TestNonStaticSingleton1.getInstance()).thenReturn(testNonStaticSingleton1Mock);

Mockito.when(testNonStaticSingleton1Mock.test1()).thenReturn(TestConstants.MOCKED);

1.1.3. Stub无参数构造函数

使用“PowerMockito.whenNew(需要Stub的构造函数所在的类.class).withNoArguments().thenReturn();”,可以对无参数构造函数进行Stub,thenReturn()方法的参数指定构造函数被Stub后返回的对象,可以使指定类的无参数构造函数返回指定的对象,可为被Mock的对象,如下所示:

TestNonStaticNoArg1 testNonStaticNoArg1Mock = Mockito.mock(TestNonStaticNoArg1.class);
Mockito.when(testNonStaticNoArg1Mock.test1()).thenReturn(TestConstants.MOCKED);

PowerMockito.whenNew(TestNonStaticNoArg1.class).withNoArguments().thenReturn(testNonStaticNoArg1Mock);
1.1.3.1. 在测试代码调用构造函数

在测试代码执行被Stub的构造函数时,可以不使用@PrepareForTest注解,Stub也能够生效,生成对象为Stub指定的对象。

例如执行上述代码对TestNonStaticNoArg1类的无参数构造函数进行Stub,当执行new TestNonStaticNoArg1()生成TestNonStaticNoArg1类的实例时,生成的实例为上述指定的Mock对象,test1()方法的返回值为TestConstants.MOCKED。

可参考示例TestNStMConstructorNoArgNoPrepare类test1方法。

1.1.3.2. 在被测试代码调用构造函数

通常情况下,被Stub的构造函数是在被测试代码中执行的,而不是在测试代码中执行的。因此主要需要考虑在被测试代码调用被Stub构造函数的情况。

在被测试代码执行被Stub的构造函数,在测试代码中不使用@PrepareForTest注解时,Stub不生效,生成对象为原始对象。可参考示例TestNStMConstructorNoArgNoPrepare类test2方法。

在被测试代码执行被Stub的构造函数,需要在测试代码的类级别或方法级别,使用@PrepareForTest注解指定调用构造函数的类,Stub才能生效,生成对象为Stub指定的对象。

示例如下,在被测试代码TestService1Impl类的genNoArg1()方法中调用了TestNonStaticNoArg1类的构造函数,因此需要在测试代码使用@PrepareForTest注解指定TestService1Impl类。可参考示例TestNStMConstructorNoArgPrepareClass、TestNStMConstructorNoArgPrepareMethod类。

// 测试代码
@PrepareForTest({TestService1Impl.class})

// 被测试代码
public class TestService1Impl implements TestService1 {

    @Override
    public TestNonStaticNoArg1 genNoArg1() {
        return new TestNonStaticNoArg1();
    }
}
1.1.3.3. 在被测试代码的内部类调用构造函数

被Stub的构造函数有可能在被测试代码的内部类中执行,例如在被测试代码中创建线程,在线程中调用构造函数;或在被测试代码中的命名内部类中调用构造函数,可参考被测试代码TestNonStatic1类test1、test2方法。

在被测试代码的内部类调用构造函数,在测试代码中不使用@PrepareForTest注解时,对构造函数的Stub不生效,生成对象为原始对象,可参考示例TestNStMConstructorNoArgNoPrepare2类。

在被测试代码的内部类调用构造函数,在测试代码中使用@PrepareForTest注解的value参数指定调用构造函数的内部类所在的类时,对构造函数的Stub不生效,生成对象为原始对象,可参考示例TestNStMConstructorNoArgNoPrepare2类。

在被测试代码的内部类调用构造函数,需要在测试代码中使用@PrepareForTest注解的fullyQualifiedNames参数指定调用构造函数的类所在的包,对构造函数的Stub才能生效,生成对象为Stub指定的对象。

示例如下,在被测试代码TestNonStatic1类test1()方法中创建线程执行TestNonStaticNoArg1类的构造函数,因此需要在测试代码使用@PrepareForTest注解的fullyQualifiedNames参数指定TestNonStatic1类所在的包。

// 测试代码
@PrepareForTest(fullyQualifiedNames = "com.adrninistrator.non_static.*")

// 被测试代码
public class TestNonStatic1 {
    private TestNonStaticNoArg1 testNonStaticNoArg1 = null;

    public TestNonStaticNoArg1 test1() {
        Thread thread = new Thread(() -> testNonStaticNoArg1 = new TestNonStaticNoArg1());
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            logger.error("error: ", e);
            Thread.currentThread().interrupt();
        }

        return testNonStaticNoArg1;
    }
}

示例如下,在被测试代码TestNonStatic1类的内部类TestNonStatic1Inner的testNew()方法中调用了TestNonStaticNoArg1类的构造函数,在TestNonStatic1类的test2()方法中调用了内部类TestNonStatic1Inner的testNew()方法,因此需要在测试代码使用@PrepareForTest注解的fullyQualifiedNames参数指定TestNonStatic1类所在的包。

// 测试代码
@PrepareForTest(fullyQualifiedNames = "com.adrninistrator.non_static.*")

// 被测试代码
public class TestNonStatic1 {
    public TestNonStaticNoArg1 test2() {
        return new TestNonStatic1Inner().testNew();
    }

    private class TestNonStatic1Inner {

        private TestNonStaticNoArg1 testNew() {
            return new TestNonStaticNoArg1();
        }
    }
}

可参考示例TestNStMConstructorNoArgPrepareName类。

关于@PrepareForTest注解的fullyQualifiedNames参数说明可参考前文。

1.1.4. Stub有参数构造函数

1.1.4.1. 有参数构造函数的Stub条件

对有参数构造函数进行Stub时,Stub条件可以使用PowerMockito.whenNew().withAnyArguments()、或PowerMockito.whenNew().withArguments()等方法。

  • Stub条件为任意参数

在对有参数构造函数进行Stub时,使用PowerMockito.whenNew().withAnyArguments()方法,可将对构造函数的Stub条件设置为,调用构造函数时使用任意参数均进行Stub。示例如下,可参考示例TestNStMConstructorWithArgNPAny类test1、test2方法,TestNStMConstructorWithArgWPAny类。

TestNonStaticWithArg1 testNonStaticWithArg1Mock = Mockito.mock(TestNonStaticWithArg1.class);
Mockito.when(testNonStaticWithArg1Mock.test1()).thenReturn(TestConstants.MOCKED);

PowerMockito.whenNew(TestNonStaticWithArg1.class).withAnyArguments().thenReturn
            (testNonStaticWithArg1Mock);
  • Stub条件为指定数量及指定类型的参数

在对有参数构造函数进行Stub时,使用PowerMockito.whenNew().withArguments()方法,并将withArguments()方法的参数设置为Mockito.any(参数类型.class),可将Stub条件设置为在调用构造函数时,若使用的参数数量类型满足要求则进行Stub。示例如下,可参考示例TestNStMConstructorWithArgNPAnyType类test1方法,TestNStMConstructorWithArgWPAnyType类test1方法。

TestNonStaticWithArg1 testNonStaticWithArg1Mock = Mockito.mock(TestNonStaticWithArg1.class);
Mockito.when(testNonStaticWithArg1Mock.test1()).thenReturn(TestConstants.MOCKED);

PowerMockito.whenNew(TestNonStaticWithArg1.class).withArguments(Mockito.anyString()).thenReturn(testNonStaticWithArg1Mock);
  • Stub条件为指定值的参数

在对有参数构造函数进行Stub时,使用PowerMockito.whenNew().withArguments()方法,并将withArguments()方法的参数设置为指定值,可将Stub条件设置为在调用构造函数时,若使用的参数等于指定值则进行Stub。示例如下,可参考示例TestNStMConstructorWithArgNPSpecified类test1方法,TestNStMConstructorWithArgWPSpecified类test1方法。

TestNonStaticWithArg1 testNonStaticWithArg1Mock = Mockito.mock(TestNonStaticWithArg1.class);
Mockito.when(testNonStaticWithArg1Mock.test1()).thenReturn(TestConstants.MOCKED);

PowerMockito.whenNew(TestNonStaticWithArg1.class).withArguments(TestConstants.FLAG1).thenReturn
        (testNonStaticWithArg1Mock);

withArguments()方法可以使用ArgumentMatchers接口的其他方法作为有参数构造函数的Stub条件,略。

1.1.4.2. 使用@PrepareForTest注解

Stub有参数构造函数时,对@PrepareForTest注解的使用情况与无参数构造函数类似。

  • 在测试代码调用构造函数,可以不使用@PrepareForTest注解,Stub也能够生效。

对于Stub条件为任意参数,可参考示例TestNStMConstructorWithArgNPAny类test1、test2方法,

对于Stub条件为指定数量及指定类型的参数,可参考示例TestNStMConstructorWithArgNPAnyType类test1方法,

对于Stub条件为指定值的参数,可参考示例TestNStMConstructorWithArgNPSpecified类test1方法;

  • 在被测试代码执行被Stub的构造函数,需要在测试代码的类级别或方法级别使用@PrepareForTest注解指定被测试代码对应的类,Stub才能生效。

对于Stub条件为任意参数,可参考示例TestNStMConstructorWithArgNPAny类test3方法,TestNStMConstructorWithArgWPAny类;

对于Stub条件为指定数量及指定类型的参数,可参考示例TestNStMConstructorWithArgNPAnyType类test3方法,TestNStMConstructorWithArgWPAnyType类test1方法;

对于Stub条件为指定值的参数,可参考示例TestNStMConstructorWithArgWPSpecified类test1方法。

  • 在被测试代码的内部类调用构造函数,需要在测试代码中使用@PrepareForTest注解的fullyQualifiedNames参数指定构造函数所在的类所在的包,Stub才能生效,示例略。
1.1.4.3. 不满足有参数构造函数Stub条件

当使用PowerMockito.whenNew().withArguments()方法对有参数构造函数进行Stub时,当调用构造函数时的参数不满足Stub条件时,生成的对象为null。

对于Stub条件为指定数量及指定类型的参数,可参考示例TestNStMConstructorWithArgNPAnyType类test2方法,TestNStMConstructorWithArgWPAnyType类test2方法;

对于Stub条件为指定值的参数,可参考示例TestNStMConstructorWithArgNPSpecified类test2方法,TestNStMConstructorWithArgWPSpecified类test2方法。

1.1.5. 不支持对原始对象进行Stub

Stub操作需要对Mock或Spy对象执行,不支持对原始对象执行。当对原始对象进行Stub时,会出现异常。

例如对TestNonStatic1类的原始对象进行Stub,会出现以下异常:

TestNonStatic1 testNonStatic1 = new TestNonStatic1();

Mockito.when(testNonStatic1.test1()).thenReturn(null);
org.mockito.exceptions.misusing.MissingMethodInvocationException
when() requires an argument which has to be 'a method call on a mock'.

可参考示例TestNStMockRaw类。

1.2. Spy后Stub非静态方法

1.2.1. 生成非静态方法对应的类的Spy对象

参考“13. Spying on real objects”( https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/Mockito.html#spy )。

Mockito类的spy()方法可以创建真实对象的Spy对象。Spy对象未被Stub的方法会执行真实方法。 Mockito不会将调用委托给传入的真实实例,而是创建真实实例的副本。当调用Spy对象的未Stub方法而不是调用真实实例的对应方法时,不会对真实实例产生影响。

1.2.2. Mockito.spy()方法比较

Mockito支持<T> T spy(T object)与<T> T spy(Class<T> classToSpy)方法,参数分别需要传入对象实例,或类的Class对象。Mockito类的spy()方法不支持传入Answer、MockSettings等参数。

  • 对实例执行spy

Mockito.spy()方法支持对某个类的实例执行,示例如下,可参考示例TestNStSpyCompare类test1方法。

TestNonStaticNoArg1 testNonStaticNoArg1Spy = Mockito.spy(new TestNonStaticNoArg1());
  • 对包含无参数构造函数类的Class对象执行Spy

Mockito.spy()方法支持对包含无参数构造函数类的Class对象执行, 会执行无参数构造函数 ,示例如下,可参考示例TestNStSpyCompare类test2方法。

TestNonStaticNoArg1 testNonStaticNoArg1Spy = Mockito.spy(TestNonStaticNoArg1.class);
  • 对不包含无参数构造函数类的Class对象执行Spy

Mockito.spy()方法不支持对不包含无参数构造函数类的Class对象执行,会出现异常。示例如下,可参考示例TestNStSpyCompare类test3方法。

Mockito.spy(TestNonStaticWithArg1.class);

异常信息示例如下:

org.mockito.exceptions.base.MockitoException
Unable to create mock instance of type 'TestNonStaticWithArg1'
org.mockito.creation.instance.InstantiationException
Unable to create instance of 'TestNonStaticWithArg1$MockitoMock$1139741189'.
Please ensure that the target class has a 0-arg constructor.

1.3. 对非静态方法进行Suppress

使用PowerMockito.suppress()方法支持对非静态方法进行Suppress。

1.3.1. Suppress构造函数

使用PowerMockito类的void suppress(Constructor<?> constructor)方法,可以对单个构造函数进行Suppress,使用PowerMockito类的void suppress(Constructor<?>[] constructors)方法,可以对构造函数数组进行Suppress。

被Suppress的构造函数会被禁止,调用被Suppress的构造函数时什么也不会做。

禁止构造函数不需要使用@PrepareForTest注解。

1.3.1.1. Suppress唯一的构造函数

使用PowerMockito.constructor()方法可以获取指定类的唯一的构造函数,在使用时需要指定类的Class对象,返回类型为Constructor<T>,当指定类存在多个构造函数时,会抛出TooManyConstructorsFoundException异常。

使用PowerMockito.suppress()方法Suppress类的唯一构造函数示例如下:

PowerMockito.suppress(PowerMockito.constructor(TestNonStatic3.class));

当类只包含一个构造函数时,Suppress成功,可参考示例TestNStSuppressConstructor1类test1方法。

当类只包含多个构造函数时,PowerMockito.constructor()方法会出现异常,异常信息示例如下。可参考示例TestNStSuppressConstructor1类test2方法。

org.powermock.reflect.exceptions.TooManyConstructorsFoundException
Several matching constructors found, please specify the argument parameter types so that PowerMock can determine which method you're referring to.
Matching constructors in class com.adrninistrator.non_static.TestNonStatic4 were:
com.adrninistrator.non_static.TestNonStatic4( )
com.adrninistrator.non_static.TestNonStatic4( java.lang.String.class )
1.3.1.2. Suppress全部的构造函数

使用PowerMockito.constructorsDeclaredIn()方法可以获取指定类的全部构造函数,返回类型为Constructor<?>[],在使用时需要指定类的Class对象。

使用PowerMockito.suppress()方法Suppress类的全部构造函数示例如下,可参考示例TestNStSuppressConstructorsDeclaredIn1类。

PowerMockito.suppress(PowerMockito.constructorsDeclaredIn(TestNonStatic3.class));
1.3.1.3. Suppress默认的构造函数

使用PowerMockito.defaultConstructorIn()方法可以获取指定类的默认构造函数,返回类型为Constructor<?>[],当指定的类不存在默认构造函数时,会抛出ConstructorNotFoundException异常,在使用时需要指定类的Class对象。

使用PowerMockito.suppress()方法Suppress类的默认构造函数示例如下:

PowerMockito.suppress(PowerMockito.defaultConstructorIn(TestNonStatic3.class));
  • 仅存在一个无参数构造函数类

对于仅存在一个无参数构造函数的类,PowerMockito.defaultConstructorIn()方法可获取到无参数构造函数,对其Suppress成功。可参考示例TestNStSuppressDefaultConstructorIn1类test1方法。

  • 存在多个构造函数的类

对于存在多个构造函数的类,PowerMockito.defaultConstructorIn()方法可获取到无参数构造函数,对其Suppress成功,对于有参数构造函数不会Suppress。可参考示例TestNStSuppressDefaultConstructorIn1类test2方法。

  • 无构造函数的类

对于无构造函数的类,会自动添加一个无参数构造函数,PowerMockito.defaultConstructorIn()方法可获取到无参数构造函数,对其Suppress成功。可参考示例TestNStSuppressDefaultConstructorIn1类test3方法。

  • 存在有参数构造函数,但不存在无参数构造函数的类

对于存在有参数构造函数,但不存在无参数构造函数的类,不会自动添加一个无参数构造函数,PowerMockito.defaultConstructorIn()方法无法获取到无参数构造函数,会出现异常,异常信息如下所示。可参考示例TestNStSuppressDefaultConstructorIn1类test3方法。

org.powermock.reflect.exceptions.ConstructorNotFoundException
Couldn't find a default constructor in com.adrninistrator.non_static.TestNonStaticWithArg2.
1.3.1.4. 使用SuppressCode.suppressConstructor()进行Suppress

PowerMockito.suppress()方法可以使用SuppressCode.suppressConstructor()方法替代,可参考示例TestNStSuppressConstructor2、TestNStSuppressConstructorsDeclaredIn2、TestNStSuppressDefaultConstructorIn2类。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值