16

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?

mate00
  • 2,727
  • 5
  • 26
  • 34

3 Answers3

24

@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.

Enrico Giurin
  • 2,183
  • 32
  • 30
xyz
  • 5,228
  • 2
  • 26
  • 35
  • 2
    Thanks. It looks like not much profit then. I can list all my mocks in class level, but I have to autowire them anyway... – mate00 May 15 '19 at 08:32
7

The shortest variant in your case is @MockBean which support multiple values of required classes of mocks

@MockBean({ShoppingService.class, ShopRepository.class})
borino
  • 1,720
  • 1
  • 16
  • 24
3

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 @Repeatable(MockBeans.class) Annotation Type @MockBean
  • Containing Annotation Type @MockBeans
yvoytovych
  • 871
  • 4
  • 12