Sonka
Sonka

Reputation: 92

Should @Autowired and @SpringBootTest be used in unit tests?

In a project where I work, we have been initializing Services for Unit testing in the following way:

  1. Mock dependencies that are needed for the Service.
  2. Create the Service using a constructor.

Something like this :

@RunWith(SpringRunner.class)
public class ServiceTest extends AbstractUnitTest {

  @Mock private Repository repository;
  private Service service;

  @Before
  public void init() {
    service = new Service(repository);
    when(repository.findById(any(Long.class))).thenReturn(Optional.of(new Entity()));
  }
}

But our new developer proposed to use @Autowired and @SpringBootTest

@SpringBootTest(classes = ServiceTest.class)
@MockBean(classes = Repository.class)
@RunWith(SpringRunner.class)
public class ServiceTest extends AbstractUnitTest {

  @MockBean private Repository repository;
  @Autowired private Service service;

  @Before
  public void init() {
    when(repository.findById(any(Long.class))).thenReturn(Optional.of(new Entity()));
  }
}

Before that, I supposed that @Autowired and @SpringBootTest should be used only in integration tests. But googled a lot and I see that some people use those two in Unit tests. I read boot-features-testing. Also, I read this Unit tests vs integration tests with Spring. For me, it still doesn`t feel well that we need to involve Spring to do dependency injection for unit tests, as we can do this by ourselves to do the unit testing. So, should @Autowired and @SpringBootTest be used in unit tests?

Upvotes: 4

Views: 3673

Answers (2)

groovedigga
groovedigga

Reputation: 252

In TDD tests should be helpful, straight forward, fast and keep maintenance at minimum. Otherwise devs will be annoyed and try to avoid tests.
So I recommend not to be to strict if it's a pure unit-test or a bit of a integration. Choose the test-scope that suits best for your situation and use the technical features that fit into this scope.

Don't use DI if you are doing a "real" unittest, testing a self-contained method on its own. Those tests make sense if the method does something meaningful, e.g. algorithm, calculation, decision making. Mocking sources of data is great here to get predictible input values. The downside of a @SpringBootTest that you don't really need is a hell of a startup-time (depends on project size) which is really annoying.

Use CDI if a method calls functionality on a dependency. 1) Setting myService.service2 = new Service2() by hand leaves you with a Service2 that is also not handled by a DI-Container which maybe requires you to set some more dependencies... 2) CDI in testing is a breeze with Spring - so why would you bloat your tests with setup-code? 3) DI involves proxies which sometimes behave a little different from simple instances.

Use @ContextConfiguration(classes = {ServiceTest.class}) to get CDI with a faster startup compared to @SpringBootTest.

Don't test glueing code with unittests as it does not have any intrinsic value. Those tests are hard to understand (who loves documenting a test?), will require a lot of mocking and will often be subject to change. Test such code in association with other methods/classes even if it means you only have integration tests for these parts of the code.

Upvotes: 1

No. A unit test is to test a single component in isolation. Using constructor injection in your beans allows you to very simply call new SomeService(myMock), no Spring required.

Writing component or functional tests (testing your application but not wiring it up to external services for a full integration test, mocking only external interfaces; this is good for things like MockMvc tests) is a good match for @SpringBootTest, and in that case you may need to create mock objects in a Spring configuration and autowire them into your test so you can manipulate them.

Upvotes: 8

Related Questions