【Mockito】Mockito工具的使用

文章目录

      • 1 什么是Mockito?
      • 2 为什么使用Mockito?
      • 3 如何使用Mockito?
        • 3.1 Mockito的基本对象
        • 3.2 Mockito简单使用Demo
        • 3.3 关于@InjectMock的注意事项
          • 3.3.1、InjectingAnnotationEngine#injectMock
          • 3.3.2、DefaultInjectionEngine#injectMocksOnFields
          • 3.3.3、MockInjectionStrategy的三种注入策略实现
      • 参考博客

单元测试目的是在不涉及依赖关系的情况下测试代码(隔离性),所以测试代码与其他类或者系统的关系应该尽量被消除。最简单的方式使用模拟类来替换实际的业务类。这里主要讲在SpringBoot下使用Mockito,其实都一样,只要是junit都可以使用Mockito的,SringBootTest Stater会自动依赖Mockito的。

1 什么是Mockito?

Mockito是一款提供模拟测试的工具包,提供了对java单元测试对象的模拟、断言等等能力。

2 为什么使用Mockito?

使用Mockeito可以为我们的单元测试提供一个模拟的无依赖的测试环境。让我们在单元测试的时候只关注当前的业务代码而不用考虑依赖项对主流程的影响。

3 如何使用Mockito?

Mockito的使用很简单,直接配合Junit使用即可,SpringBootTest starter集成了Mockito和Junit,如果单独使用记得引人Junit和Mockito依赖。

Mockito的使用,在进行测试的时候更具需要使用就好了,学习的话可以看官方提供的问题,因为Mockito使用的行为驱动模式,所以直接看API来的更快。

3.1 Mockito的基本对象

  • mock()/@Mock:创建一个完全模拟对象
    • 通过Answer/MockSettings指定操作行为
    • when()/give()指定模拟行为
    • 也可以自定义Answer
  • spy()/@Spy:创建一个间谍对象,非指定返回模拟结果的方法,会调用对象的实际方法
  • @InjectMocks:自动注入被@Mock or @Spy注解的属性
  • verify():验证某些行为至少发生一次/确切次数/永不发生

3.2 Mockito简单使用Demo

注意:这里需要导入Mockito、SpringTest、Junit、lombok的依赖

package com.lfsfenior.rocketmq;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;

@RunWith(SpringJUnit4ClassRunner.class)
public class MockioTest {
    /* 使用@InjectMocks会自动注入被@Mock和@Spy注解的对象 */
    @InjectMocks
    private MockTcCls mockTcCls;

    /* 使用@Mock注解创建完全模拟对象 */
    @Mock
    public QueryProductDetailService queryProductDetailService;

    /* 使用@Spy创建中间对象,对于非指定模拟的方法,将调用实例的真实方法 */
    @Spy
    public TcProductService tcProductService;


    @Before
    public void init() throws Exception {
        /* 为Mock对象指定行为 */
        given(mockTcCls.queryProductDetailService.queryProductDetail(any())).willReturn("模拟返回");
        
        /* 为Spy对象定制返回结果 */
        when(mockTcCls.tcProductService.isOldOrNew(any())).thenAnswer((mock) -> {
            String argument = mock.getArgument(0);
            if (argument.startsWith("PD")) {
                return "模拟返回";
            }
            return mock.callRealMethod();
        });
    }

    @Test
    public void mockito() throws Exception {
        System.out.println(mockTcCls.queryProductDetailService.queryProductDetail("张三"));
        System.out.println(mockTcCls.tcProductService.isOldOrNew("PD1234"));
        System.out.println(mockTcCls.tcProductService.isOldOrNew("XLS1234"));
    }
}

@AllArgsConstructor
@Setter
@Getter
class MockTcCls {
    /* 使用@Mock注解创建完全模拟对象 */
    @Mock
    public QueryProductDetailService queryProductDetailService;

    /* 使用@Spy创建中间对象,对于非指定模拟的方法,将调用实例的真实方法 */
    @Spy
    public TcProductService tcProductService;
}

class QueryProductDetailService {
    public String queryProductDetail(String name) {
        return "当前类返回";
    }
}

class TcProductService {
    public String isOldOrNew(String prd) {
        return "当前类返回";
    }
}

3.3 关于@InjectMock的注意事项

从源码我们可以看到对于@InjectMocks的处理,整体逻辑还是比较简单的,只是代码比较绕,跟一遍代码就好了。

其次我们知道Mockito的容器里对象就是最外层注册的对象,然后使用Contract注入的方式注入的,如果没有则会采用Properties注入方式的。

3.3.1、InjectingAnnotationEngine#injectMock

对于class解析,获取需要进行解析的对象以及需要虚拟的属性

    public void injectMocks(final Object testClassInstance) {
        Class<?> clazz = testClassInstance.getClass();
        Set<Field> mockDependentFields = new HashSet<Field>();
        Set<Object> mocks = newMockSafeHashSet();

        while (clazz != Object.class) {
            // 获取被@InjectMocks注入的对象
            new InjectMocksScanner(clazz).addTo(mockDependentFields);
            // 获取被@InjectMocks注入的对象中被@Mock和@Spy注解的属性
            new MockScanner(testClassInstance, clazz).addPreparedMocks(mocks);
            // 什么也没做就是留空的
            onInjection(testClassInstance, clazz, mockDependentFields, mocks);
            clazz = clazz.getSuperclass();
        }
        // 开始处理
        new DefaultInjectionEngine().injectMocksOnFields(mockDependentFields, mocks, testClassInstance);
    }
3.3.2、DefaultInjectionEngine#injectMocksOnFields

从源码我们知道,首先创建了OngoingMockInjection对象,并绑定必要的属性值。然后链式的绑定了三种注入策略,ConstructorInjection、PropertyOrFieldInjection、SpyOnInjectedFieldsHandler最核心的便是apply()方法了。

其次注入策略采用了责任链的方式。


    public void injectMocksOnFields(Set<Field> needingInjection, Set<Object> mocks, Object testClassInstance) {
        MockInjection.onFields(needingInjection, testClassInstance)
                .withMocks(mocks)
                // 加载构造函数注入方式
                .tryConstructorInjection()
                // 加载属性注入方式
                .tryPropertyOrFieldInjection()
                // 加载对@Spy的注入
                .handleSpyAnnotation()
                .apply();
    }
    
    public void apply() {
        for (Field field : fields) {
            // 构造器注入、属性注入
            injectionStrategies.process(field, fieldOwner, mocks);
            // 处理@Spy的注入策略
            postInjectionStrategies.process(field, fieldOwner, mocks);
        }
    }
    
    
3.3.3、MockInjectionStrategy的三种注入策略实现

【Mockito】Mockito工具的使用_第1张图片

  • ConstructorInjection

这里有个注意事项就是,要使用这个策略,那么这个对象必须有一个包含所有参数的构造器。主要的目的是讲@InjectMocks属性类进行实例化的。

底层也很简单,就是利用反射来初始化对象的,并且这个对象的所有属性都是空值,也就是说最后需要通过PropertyAndSetterInjection来讲属性绑定的

构造器注入的核心就是他的实例化方法initialize()-acquireFieldInstance()


    private FieldInitializationReport acquireFieldInstance() throws IllegalAccessException {
        Object fieldInstance = field.get(fieldOwner);
        if(fieldInstance != null) {
            return new FieldInitializationReport(fieldInstance, false, false);
        }
        //实例化对象,如果我们有全参数的构造函数就可以是显示依赖注入了
        return instantiator.instantiate();
    }
    
    // 实例化对象
    public FieldInitializationReport instantiate() {
        final AccessibilityChanger changer = new AccessibilityChanger();
        Constructor<?> constructor = null;
        try {
            constructor = biggestConstructor(field.getType());
            changer.enableAccess(constructor);
            
            // 获取绑定用的参数,来源就是初始化存入容器的对象
            final Object[] args = argResolver.resolveTypeInstances(constructor.getParameterTypes());
            Object newFieldInstance = constructor.newInstance(args);
            setField(testClass, field,newFieldInstance);

            return new FieldInitializationReport(field.get(testClass), false, true);
        } catch (IllegalArgumentException e) {
            throw new MockitoException("internal error : argResolver provided incorrect types for constructor " + constructor + " of type " + field.getType().getSimpleName(), e);
        } catch (InvocationTargetException e) {
            throw new MockitoException("the constructor of type '" + field.getType().getSimpleName() + "' has raised an exception (see the stack trace for cause): " + e.getTargetException().toString(), e);
        } catch (InstantiationException e) {
            throw new MockitoException("InstantiationException (see the stack trace for cause): " + e.toString(), e);
        } catch (IllegalAccessException e) {
            throw new MockitoException("IllegalAccessException (see the stack trace for cause): " + e.toString(), e);
        } finally {
            if(constructor != null) {
                changer.safelyDisableAccess(constructor);
            }
        }
    }
    

  • ProopertyAndSetterInjection

这个就是就是自动注入@Mock和@Spy对象了,走到最后你会发现,为null,为什么?
因为注入的前提是你有才能注入,没有我注入什么呢?对吧。

所以注入的前提是容器里得有这个对象才能注入到@InjectMocks注解的对象中

通过翻阅入口代码mockCandidates实际来源是初始化的时候存入的测试类的被@Mock和@Spy注解的属性,mocks

    // 通过扫描当前测试类中被@Spy和@Mock注解的类,加入到全局容器mocks对象中,方便@InjectMocks注入依赖
    public void addPreparedMocks(Set<Object> mocks) {
        mocks.addAll(scan());
    }
    
    public boolean processInjection(Field injectMocksField, Object injectMocksFieldOwner, Set<Object> mockCandidates) {
        FieldInitializationReport report = initializeInjectMocksField(injectMocksField, injectMocksFieldOwner);

        // for each field in the class hierarchy
        boolean injectionOccurred = false;
        Class<?> fieldClass = report.fieldClass();
        Object fieldInstanceNeedingInjection = report.fieldInstance();
        while (fieldClass != Object.class) {
            injectionOccurred |= injectMockCandidates(fieldClass, fieldInstanceNeedingInjection, newMockSafeHashSet(mockCandidates));
            fieldClass = fieldClass.getSuperclass();
        }
        return injectionOccurred;
    }
    
    // 然后就是简单的过滤然注入到对象中
    private boolean injectMockCandidatesOnFields(Set<Object> mocks,
                                                 Object injectee,
                                                 boolean injectionOccurred,
                                                 List<Field> orderedCandidateInjecteeFields) {
        for (Iterator<Field> it = orderedCandidateInjecteeFields.iterator(); it.hasNext(); ) {
            Field candidateField = it.next();
            Object injected = mockCandidateFilter.filterCandidate(mocks, candidateField, orderedCandidateInjecteeFields, injectee)
                                                 .thenInject();
            if (injected != null) {
                injectionOccurred |= true;
                mocks.remove(injected);
                it.remove();
            }
        }
        return injectionOccurred;
    }
  • SpyOnInjectedFieldsHandler
    // 这个处理就比较简单了
    protected boolean processInjection(Field field, Object fieldOwner, Set<Object> mockCandidates) {
        FieldReader fieldReader = new FieldReader(fieldOwner, field);

        // TODO refoctor : code duplicated in SpyAnnotationEngine
        if(!fieldReader.isNull() && field.isAnnotationPresent(Spy.class)) {
            try {
                Object instance = fieldReader.read();
                if (MockUtil.isMock(instance)) {
                    // A. instance has been spied earlier
                    // B. protect against multiple use of MockitoAnnotations.initMocks()
                    Mockito.reset(instance);
                } else {
                    Object mock = Mockito.mock(instance.getClass(), withSettings()
					    .spiedInstance(instance)
					    .defaultAnswer(Mockito.CALLS_REAL_METHODS)
					    .name(field.getName()));
					setField(fieldOwner, field, mock);
                }
            } catch (Exception e) {
                throw new MockitoException("Problems initiating spied field " + field.getName(), e);
            }
        }

        return false;
    }

参考博客

Mockito

你可能感兴趣的:(【常用工具】,Java单元测试,Mockito使用)