Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slow unit testing in spring-boot application

I'm still new to unit testing. I started to read a book about it. But one of the most important thing is that a test hast to be first (Fast,Isolated,Repeatable,Self-validating,Timely).

Okay, now I was ready for a bit of practice. But when I was building unit test in spring boot. I like to keep them separated.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = UnitTestApplication.class, loader = SpringApplicationContextLoader.class)
@WebIntegrationTest("server.port:9000")
public class FirstTestClassTest{

    ...

}


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = UnitTestApplication.class, loader = SpringApplicationContextLoader.class)
@WebIntegrationTest("server.port:9001")
public class SecondTestClassTest{

    ...

}

This gives me the problem that for every test class there is a new instance started of the application.

So let's say I introduce a new feature and want to test for bugs. I use mvn test form the command line. Then all the test are run but i think it will take ages for a real application with a lot of tests.

I there a way that there is only one instance started and keep the test fast but where i can keep the test in separate classes?

like image 626
Greg Avatar asked Jun 12 '16 09:06

Greg


Video Answer


2 Answers

I'll refer your sentence:

So let's say I introduce a new feature and want to test for bugs. I use mvn test form the command line. Then all the test are run but i think it will take ages for a real application with a lot of tests.

This question you raise here is very important, so I'll try to provide an architectural point of view here without delving into technical implementation.

There are many different kinds of tests. What you're referring to is called an integration test, and not a unit test. The difference between those two that in unit tests you're testing one class and all the rest should be mocked (there are frameworks like EasyMock or Mockito for this).

Unit test has a lot of restrictions:

  • You cannot access any external components (web servers, db and so forth)
  • Unit tests run only in memory
  • They are extremely fast (particle of second).

These tests are very helpful when it comes to testing the functionality of your classes

Now when the development of the feature is done, its time to test that the feature works in "semi-real" environment. Usually when running those, there is a database available as well as spring container. For running this kind of tests you can use spring tests extensions and use different techniques (for example, substitute one the whole application spring context with a "partial" context or override some beans with stubs or something.

Now, this difference is interesting in the context of your question because there should be many unit tests and substantially less integration tests in the application. So if you can cover something you've written with unit tests - write unit tests. It's incredibly fast by nature and you can run a lot of those (thousands) in no-time. Only if you (rarely) cannot cover your functionality in unit tests - write the integration test. This will typically relevant for code that runs against the DB for example and you would like to test whether the queries produced by the component are really valid.

So generally if you have both types of tests you should be covered and there should be not so many integration tests, so running those shouldn't be a real hassle.

like image 127
Mark Bramnik Avatar answered Oct 16 '22 13:10

Mark Bramnik


One of the ways to make spring mvc tests much faster is to use MockMvcBuilders.standaloneSetup With this setup you use not a real but a mocked application context. This setup has a very reach configuration options and in my experience, it is sufficient for most of controller tests.

That way you need to create your controller instances and inject them with mocked dependencies by hand. Here is an example of MockMvc unit test with standalone setup.

// setup beans and mockMvc
UserDAO userDAO = mock(UserDAO.class);
UserController userController = new UserController(userDAO);
MockMvc mockMvc = standaloneSetup(userController)
             .setViewResolvers(new WebConfig().viewResolver())
             .build();
...

// do test
mockMvc.perform(get(USERS))
            .andExpect(view().name(USERS_VIEW_NAME))
            .andExpect(model().attribute(TOTAL, 20))
like image 31
Andrei Bardyshev Avatar answered Oct 16 '22 12:10

Andrei Bardyshev