Reputation: 2945
I have a controller class that makes use of several services. I write a test for that controller like:
@RunWith(SpringRunner.class)
@WebMvcTest(value = PurchaseController.class, secure = false)
public class PurchaseControllerTest {
@MockBean
private ShoppingService shoppingService;
@MockBean
private ShopRepository shopRepository;
@MockBean
private SomeOtherRepository someOtherRepository;
@Autowired
private MockMvc mockMvc;
// ... some tests goes here
Thing is, that there tend to be many of those mocks, thus many lines of code. I know this may be a sign of a code smell, but that's not my point now.
I noticed that there is also a @MockBeans
annotation that has @Target(ElementType.TYPE)
. So I thought I could try:
@RunWith(SpringRunner.class)
@WebMvcTest(value = PurchaseController.class, secure = false)
@MockBeans(value = {ShoppingService.class, ShopRepository.class})
public class PurchaseControllerTest {
but it doesn't event compile.
My question is: how we could use @MockBeans
annotation? Is it applicable to my case?
Upvotes: 18
Views: 23539
Reputation: 5407
@MockBeans
it's just a repeatable annotation for multiply of @MockBean
s. If you need to reuse this mocked bean you can put in on a class / config class. But you need to use @Autowired
for services that you what to mock . So in your case it should be :
.....
@MockBeans({@MockBean(ShoppingService.class), @MockBean(ShopRepository.class)})
public class PurchaseControllerTest {
@Autowired
ShoppingService shoppingService;
@Autowired
ShopRepository shopRepository;
.....
}
Main idea of @MockBeans
it's just repeating @MockBean
in one place. For me it might be useful only for some configuration / common class that you can reuse.
@MockBean
- create a mock , @Autowired
- is autowired bean from context , in your case ,it mark/create bean as mocked then mocked bean will be injected in your autowired field.
So if you have a lot of autowired fields with @MockBeans
(or multiply @MockBean
) you can configure is it a mock or not in one place (in @MockBeans
for class level) and you don't need change @Autowired
to @Mock
in you test class (like in you case if you remove @MockBeans
all autowired beans that are not mocked will be autowired as beans from context , and if you undo removing you will work in mocked beans (that you configured inside this annotation) ) .
If you want to avoid a lot of dependency inside one class you can extract all dependency into some parent class , but as java doesn't support multiply inheritance for class it's not always might help.
Upvotes: 27
Reputation: 1750
The shortest variant in your case is @MockBean
which support multiple values of required classes of mocks
@MockBean({ShoppingService.class, ShopRepository.class})
Upvotes: 8
Reputation: 871
Javadoc says it's used as
Container annotation that aggregates several {@link MockBean} annotations.
So you can write
@MockBeans({@MockBean(ShoppingService.class), @MockBean(ShopRepository.class)})
public class PurchaseControllerTest {
@Autowire ShoppingService
works here
Or
Can also be used in conjunction with Java 8's support for repeatable annotations
@MockBean(ShoppingService.class)
@MockBean(ShopRepository.class)
public class PurchaseControllerTest {
Java 8 enables repeatable annotations, and for compatibility reasons, repeating annotations are stored in a container annotation @MockBeans
that is automatically generated by the Java compiler. In order for the compiler to do this, you need 2 things:
@Repeatable(MockBeans.class)
Annotation Type @MockBean
@MockBeans
Upvotes: 3