ref: https://howtodoinjava.com/mockito/mockito-mock-injectmocks/
源码注释:Initializes object annotated with Mockito annotations for given testClass
https://examples.javacodegeeks.com/core-java/mockito/mockito-initmocks-example/
LinkedList mocklinkedList = Mockito.mock(LinkedList.class);
Mockito.when(mocklinkedList.get(0)).thenReturn("First Value");
Assert.assertEquals("First Value", mocklinkedList.get(0));
Mockito.verify(mocklinkedList).get(0); // verify the invocation
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.”
主要参考了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的例子理解:
原文: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.
原文: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.
原文: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.
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.
@RunWith(MockitoJUnitRunner.class)
@Rule public MockitoRule rule = MockitoJUnit.rule();
很神奇,这个时候不用用上述的那些方法init Mocks,也能使用Mockito的各种注解。
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')
}
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还是比较复杂,难免会用到一些包…
下面这些都是自己尝试不同搭配后总结的...全文最重要的就是那些有绿色标注的了吧
ps: 下面指的实例化,是指不是null,也不仅仅是个bean的名字,而是debug时可以看到成员变量等的。
在有initMocks而没有@SpringBootTest的情况下:
@Inject:Bean没有声明也没用注入,是个null
@MockBean:同@Inject一模一样,Bean没有声明也没用注入,是个null
@Mock:Bean注入/实例化了,有各个参数,还有一个mockitoInterceptor属性。但是其dependency的bean没有被实例化,是null。
@InjectMocks: 其dependency需要有@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也能正常工作了。
// 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
(意外发现如果没有@SpringBootTest(),如果有但是没有@Runwith,@Configuration文件是不会被调用的。)
在两个注解都有,并且选好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用不同注解组合的情况。
@Component
class A {
@Inject B b;
public void f1() {
b.setApiStatusCode(200);
return b;
}
}
@Data
@Component
class B {
public Integer apiStatusCode;
}
@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是个很具象的东西。
@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的情况,在前面我们讨论过。
@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.
先写这么多吧,一个个试,我要裂开了。。。但是感觉网上说的不结合例子都很难理解。总之慢慢来慢慢体会吧…说不定以后还要用错