Solved - JUnit Mockito: 比较 @Inject @InjectMocks @MockBean @Mock

ref: https://howtodoinjava.com/mockito/mockito-mock-injectmocks/

1. initMocks vs. @SpringBootTest ( 不想看这些背景介绍的可以跳过直接看2.)

1.1 在@Before中用initMocks

源码注释:Initializes object annotated with Mockito annotations for given testClass

1.1.1 先看看init mocks的多种方法:

https://examples.javacodegeeks.com/core-java/mockito/mockito-initmocks-example/

1.1.1.1 用Mocikito.mock():

  • 传入的参数可以是interface或者class
LinkedList mocklinkedList = Mockito.mock(LinkedList.class);
  • 当某个action被触发,返回需要的东西:
Mockito.when(mocklinkedList.get(0)).thenReturn("First Value");
  • 验证
Assert.assertEquals("First Value", mocklinkedList.get(0));
Mockito.verify(mocklinkedList).get(0); // verify the invocation

1.1.1.2 用initMocks()

MockitoAnnotations.initMocks(this);

“This method is useful when you have a lot of mocks to inject. It minimizes repetitive mock creation code, makes the test class more readable and makes the verification error easier to read because the field name is used to identify the mock.”

1.1.1.2.1 Inject Mocks

主要参考了https://site.mockito.org/javadoc/current/org/mockito/InjectMocks.html

@InjectMocks
Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection in order and as described below. If any of the following strategy fail, then Mockito won’t report failure; i.e. you will have to provide dependencies yourself.

下面的可结合1.1.1.2.2的例子理解:

  1. Constructor Injection
    需要被Inject的Object,其构造函数所需要的参数需要在test中被声明。如果没有被声明,则要注入的Object为null。

原文:the biggest constructor is chosen, then arguments are resolved with mocks declared in the test only. If the object is successfully created with the constructor, then Mockito won’t try the other strategies.
Note: If arguments can not be found, then null is passed. If non-mockable types are wanted, then constructor injection won’t happen. In these cases, you will have to satisfy dependencies yourself.

  1. Property setter Injection
    优先级:type > name
    如果同一type的properties有很多,那最好给它们都起个名字

原文:mocks will first be resolved by type (if a single type match injection will happen regardless of the name), then, if there is several property of the same type, by the match of the property name and the mock name.
Note 1: If you have properties with the same type (or same erasure), it’s better to name all @Mock annotated fields with the matching properties, otherwise Mockito might get confused and injection won’t happen.
Note 2: If @InjectMocks instance wasn’t initialized before and have a no-arg constructor, then it will be initialized with this constructor.

  1. Field Injection
    优先级:type > name
    如果同一type的fields有很多,那最好给它们都起个名字

原文:Field injection; mocks will first be resolved by type (if a single type match injection will happen regardless of the name), then, if there is several property of the same type, by the match of the field name and the mock name.
Note 1: If you have fields with the same type (or same erasure), it’s better to name all @Mock annotated fields with the matching fields, otherwise Mockito might get confused and injection won’t happen.
Note 2: If @InjectMocks instance wasn’t initialized before and have a no-arg constructor, then it will be initialized with this constructor.

1.1.1.2.2 Example & Analysis:
public class ArticleManagerTest extends SampleBaseTestCase {

    @Mock private ArticleCalculator calculator;
    @Mock(name = "database") private ArticleDatabase dbMock; // note the mock name attribute
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @InjectMocks private ArticleManager manager;

    @Test public void shouldDoSomething() {
        manager.initiateArticle();
        verify(database).addListener(any(ArticleListener.class));
    }
}

public class SampleBaseTestCase {
    @Before public void initMocks() {
        MockitoAnnotations.initMocks(this);
    }
}

分析@InjectMocks 的 ArticleManager:
Field - “ArticleManager” annotated with @InjectMocks can have a parameterized constructor only, or a no-arg constructor only, or both. All these constructors can be package protected, protected or private, however Mockito cannot instantiate inner classes, local classes, abstract classes and of course interfaces. Beware of private nest static classes too.

The same stands for setters or fields, they can be declared with private visibility, Mockito will see them through reflection. However fields that are static or final will be ignored.

Constructor Injection happens here:

 public class ArticleManager {
      ArticleManager(ArticleCalculator calculator, ArticleDatabase database) {
          // parameterized constructor
      }
  }

Property setter injection will happen here :

public class ArticleManager {
    // no-arg constructor
    ArticleManager() {  }

    // setter
    void setDatabase(ArticleDatabase database) { }

    // setter
    void setCalculator(ArticleCalculator calculator) { }
}

Field injection will be used here :

 public class ArticleManager {
     private ArticleDatabase database;
     private ArticleCalculator calculator;
 }

And finally, no injection will happen on the type in this case:

public class ArticleManager {
    private ArticleDatabase database;
    private ArticleCalculator calculator;

    ArticleManager(ArticleObserver observer, boolean flag) {
        // observer is not declared in the test above.
        // flag is not mockable anyway
    }
}

Again, note that @InjectMocks will only inject mocks/spies created using the @Spy or @Mock annotation. 这句我理解的是,比如ArticleManager有依赖的bean - A,如果A没有被用@Mock或者@Spy的话,A是不会被Inject的,但是ArticleManager本身用了@InjectMocks是可以被Inject的。

Mockito is not an dependency injection framework, don’t expect this shorthand utility to inject a complex graph of objects be it mocks/spies or real objects.

1.1.1.3 用MociktoJUnitRunner

@RunWith(MockitoJUnitRunner.class)

1.1.1.4 用MockitoRule

@Rule public MockitoRule rule = MockitoJUnit.rule();

1.2 在test Class上标注@SpringBootTest和@RunWith

很神奇,这个时候不用用上述的那些方法init Mocks,也能使用Mockito的各种注解。

1.2.1 @SpringBootTest

https://reflectoring.io/spring-boot-test/

With the @SpringBootTest annotation, Spring Boot provides a convenient way to start up an application context to be used in a test. That application context will contain all the objects we need for all of the above test types.

dependencies {
	testCompile('org.springframework.boot:spring-boot-starter-test')
	testCompile('org.junit.jupiter:junit-jupiter:5.4.0')
}

1.2.2 Integration Test vs. Unit Test

  1. Unit test:
    A unit commonly is a single class, but can also be a cluster of cohesive classes that is tested in combination.
  2. Integration test:
    An integration test can be any of the following:
    a) A test that covers multiple “units”. It tests the interaction between two or more clusters of cohesive classes.
    b) A test that covers multiple layers. This is actually a specialization of the first case and might cover the interaction between a business service and the persistence layer, for instance.
    c) A test that covers the whole path through the application. In these tests, we send a request to the application and check that it responds correctly and has changed the database state according to our expectations.

1.2.3 plain test

Integration test需要启动spring环境,创建一个application context,会很慢,也很冗长,所以我们有时候更希望用plain test,这是介于unit test和integration test之间的一种test,它可以test多个class,但是object graph不是通过spring application context创建的,而是我们手动创建的。

原文:So, for simple tests that cover multiple units we should rather create plain tests, very similar to unit tests, in which we manually create the object graph needed for the test and mock away the rest.

For tests that cover integration with the web layer or persistence layer, we can use @WebMvcTest or @DataJpaTest instead. For integration with other layers, have a look at Spring Boot’s other test slice annotations.

Finally, for tests that cover the whole Spring Boot application from incoming request to database, or tests that cover certain parts of the application that are hard to set up manually, we can and should use @SpringBootTest

个人感觉:
那种bean有dependency graph的,可以考虑手动建立graph;
需要@Configuration的,也可以用@ContextConfiguration(classes=)
但有时候graph还是比较复杂,难免会用到一些包…

下面这些都是自己尝试不同搭配后总结的...全文最重要的就是那些有绿色标注的了吧

2. @Inject vs. @InjectMocks vs. @MockBean vs. @Mock, in Unit and Integration test

ps: 下面指的实例化,是指不是null,也不仅仅是个bean的名字,而是debug时可以看到成员变量等的。

2.1 Unit test with initMocks() in “Junit @Before”;

在有initMocks而没有@SpringBootTest的情况下:
@Inject:Bean没有声明也没用注入,是个null
@MockBean:同@Inject一模一样,Bean没有声明也没用注入,是个null
@Mock:Bean注入/实例化了,有各个参数,还有一个mockitoInterceptor属性。但是其dependency的bean没有被实例化,是null。
@InjectMocks: 其dependency需要有@Mock注解,如下:

  1. B有Mock注解,
// A depend on B 
@Mock
private B b;  // instance
@InjectMocks
private A a; // instance with dependency b

Bean a 注入/实例化了,其依赖的bean b 也被实例化了。debug时,调用bean a其中的方法时,是可以进去这个方法的,然后它depend on的依赖的bean b也能正常工作了。

  1. B没有Mock注解
// A depend on B 
@Inject // or: @MockBean
private B b;  // null
@InjectMocks
private A a;  // instance without dependency b

b = null;
Bean a注入/实例化了,其依赖的bean b没有被实例化。

  • 正如1中有提到,被@InjectMocks标注的Object A,如果它调用了Object B,(B是A的dependency),那么B需要被@Mock所修饰,这样就手动建立了 A <- B的dependency graph。
  • 如果 A <- B <- C,那么C也要在test class中被@Mock

2.2 Integration with @SpringBootTest() on test Class.

(意外发现如果没有@SpringBootTest(),如果有但是没有@Runwith,@Configuration文件是不会被调用的。)

2.2.1 Compare

在两个注解都有,并且选好ActivateProfiles的情况下:

@Inject:可以注入/实例化bean和bean的整个dependency graph,并且也注入/实例化这些dependency的bean,这也是@Inject的常规用法

@InjectMocks:可以注入/实例化bean,但是不能注入/实例化其dependency的bean,其dependency的bean为null,没有mockitoInterceptor属性,只有本身的成员变量。但是,同2.1所说,当其dependency用以及dependency的dependency都用@Mock注解时,就可以也被注入了。

@MockBean:可以注入/实例化bean,但是不能注入/实例化其dependency的bean,其dependency的bean为null。会多一个属性:mockitoInterceptor。

@Mock:可以注入/实例化bean,但是不会实例化该bean的dependency,其dependency的bean为null。 会多一个属性:mockitoInterceptor。

ps: 我没有测试所有A<-B,A和B用不同注解组合的情况。

2.2.2 两个失败用法

@Component
class A {
	@Inject B b;
	public void f1() {
		b.setApiStatusCode(200);
		return b;
	}
}
@Data
@Component
class B {
	public Integer apiStatusCode;
}

2.2.2.1 第一个案例

@InjectMocks   // correct
private B b;
@MockBean / @Mock   // correct
private A a;

@Test
public void test1() {
	when(A.f1()).thenReturn(B)
}

如上,第一个失败的例子总结如下:

@ A B
@MockBean O x - http response status code wrong
@Mock O x - http response status code wrong
@InjectMocks x - execution exception O

A是个很抽象,B是个很具象的东西。

2.2.2.2 第二个案例

@SpringBootTest(class = ....)
@RunWith(...)
class Test {
	@InjectMocks / @Mock  // correct
	private B b;
	@Inject   // correct
	private A a;
	
	@Test
	public void test1() {
		b = A.f1();
	}
}

如上,第二个失败的例子:a调用了b,a是@Inject,b如果是@MockBean,a的方法中call了b的set方法。然后发现b并没有被set,仍是null。但是b若用@InjectMocks,@Mock都可以,都成功被set了。
用了@MockBean后,这个Object的set方法好像失效了,我是用lombok的@Data,发现并没有set,还是null

总结:
最抽象:@MockBean给人的感觉就是声明并实例化了这个bean a,然后可以用来描述这个bean的一些操作,你用到的是这个“克隆”的假bean,只是为了方便描述一些函数的触发,而不是真的要用这个bean执行一些函数。

介于之间:@Mock某种意义上说,同@MockBean上面说的类似,的确,在when(A.method).thenReturn(B)中,A是@MockBean或者是@Mock,都可以正常运行。但是好像没有提到会替换真正的bean。所以bean原来的set方法还能使用。

最具象:@InjectMocks,给人的感觉是什么时候用都没啥大问题,但是用在when(A.method).thenReturn(B)的A上,就会有问题。感觉可能A只需要个比较抽象的东西,比如MockBean,而不是一个具体的东西。而需要一个具体的class,甚至要用它的方法,那么@InjectMocks是不错的选择。

除此之外,也不用忘记@InjectMocks与@Mock一起用去引入dependency graph的情况,在前面我们讨论过。

2.2.3 @InjectMocks & @Mock, vs. @MockBean补充

@MockBean里这样说明:

Annotation that can be used to add mocks to a Spring ApplicationContext…If any existing single bean of the same type defined in the context will be replaced by the mock, if no existing bean is defined a new one will be added.

而且,MockBean是springframework…mockito提供的,而Mock是mockito包里的。

@Mock:

They allow to mock a class or an interface and to record and verify behaviors

https://howtodoinjava.com/mockito/mockito-mock-injectmocks/

@Mock annotation creates mocks
@InjectMocks creates class objects and inject mocked dependencies.

  • Use @InjectMocks to create class instances which needs to be tested in test class.
  • Use @InjectMocks when actual method body needs to be executed for a given class.
  • Use @InjectMocks when we need all internal dependencies initialized with mock objects to work method correctly.
  • Use @Mock to create mocks which are needed to support testing of class to be tested.
  • Annotated class (to be tested) dependencies with @Mock annotation.
  • We must define the when-thenRetrun methods for mock objects which class methods will be invoking during actual test execution.

先写这么多吧,一个个试,我要裂开了。。。但是感觉网上说的不结合例子都很难理解。总之慢慢来慢慢体会吧…说不定以后还要用错

你可能感兴趣的:(Solved - JUnit Mockito: 比较 @Inject @InjectMocks @MockBean @Mock)