Mastering Spring 5.0
上QQ阅读APP看书,第一时间看更新

Writing JUnit using the Spring context

In the previous sections, we looked at how to launch a Spring context from the main method. Now let's shift our attention to launching a Spring context from a unit test.

We can use SpringJUnit4ClassRunner.class as a runner to launch a Spring context:

    @RunWith(SpringJUnit4ClassRunner.class)

We would need to provide the location of the context configuration. We will use the XML configuration that we created earlier. Here's how you can declare this:

    @ContextConfiguration(locations = {  
"/BusinessApplicationContext.xml" })

We can autowire a bean from the context into the test using the @Autowired annotation. BusinessService is autowired by the type:

    @Autowired 
private BusinessService service;

As of now, DataServiceImpl, which is wired in, returns Arrays.asList(new Data(10), new Data(20)). BusinessServiceImpl calculates the sum 10+20 and returns 30. We will assert for 30 in the test method using assertEquals:

    long sum = service.calculateSum(DUMMY_USER); 
assertEquals(30, sum);
Why do we introduce unit testing so early in the book?

Actually, we believe we are already late. Ideally, we would have loved to use Test-driven development ( TDD) and write tests before code. In my experience, doing TDD leads to simple, maintainable, and testable code.

Unit testing has a number of advantages:

  • A safety net against future defects
  • Defects are caught early
  • Following TDD leads to a better design
  • Well-written tests act as documentation of code and functionality--especially those written using the BDD Given-When-Then style

The first test we will write is not really a unit test. We will load up all the beans in this test. The next test, written using mocking, will be a real unit test, where the functionality being unit tested is the specific unit of code being written.

The complete list of the test is as follows; it has one test method:

    @RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(locations = {
"/BusinessApplicationContext.xml" })
public class BusinessServiceJavaContextTest {
private static final User DUMMY_USER = new User("dummy");
@Autowired
private BusinessService service;

@Test
public void testCalculateSum() {
long sum = service.calculateSum(DUMMY_USER);
assertEquals(30, sum);
}
}

There is one problem with the JUnit that we wrote. It is not a true unit test. This test is using the real (almost) implementation of DataServiceImpl for the JUnit test. So, we are actually testing the functionality of both BusinessServiceImpl and DataServiceImpl. That's not unit testing.

The question now is this; how do we unit test BusinessServiceImpl without using a real implementation of DataService?

There are two options:

  • Create a stub implementation of the data service, providing some dummy data in the src\test\java folder. Use a separate test context configuration to autowire the stub implementation instead of the real the DataServiceImpl class.
  • Create a mock of DataService and autowire the mock into BusinessServiceImpl.

Creating a stub implementation would mean the creation of an additional class and an additional context. Stubs become more difficult to maintain, as we need more variations in data for the unit test.

In the next section, we will explore the second option of using a mock for unit testing. With the advances in mocking frameworks (especially Mockito) in the last few years, you will see that we would not even need to launch a Spring context to execute the unit test.