Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring context dirty after each integration test

I recently started as a freelancer on my current project. One of the thing I threw myself on, was the failing Jenkins build (it was failing starting from April 8th, a week before I started here).

Generally speaking, you could see a buttload of DI issues in the log. First thing I did, was get all tests to work in the same way, starting from the same application context. They also implemented their own "mocking" thing, which seemed to fail to work correctly. After a discussion with the lead dev, I suggested to start using Springockito. (for a certain module, they needed mocking for their integration testing -- legacy reasons, which can't be changed)

Anyway, stuff started failing badly after that. A lot of beans which were mocked in the test, simply weren't mocked, or weren't found or whatever. Typically, it would fail on the loading of the application context, stating that one or another bean was missing.

I tried different stuff and different approaches, but in the end, only the thing I most feared would work: add @DirtiesContext to every single test. Now, the maven build is starting to turn green again, tests start doing what they are supposed to do. But I am reloading the Spring context each and every time, which takes time - which is all relative, since the context is loaded in about 1 - 2 seconds.

A side note to this story is that they've upgraded to Hibernate 4, and thus to Spring 3.2. Previously, they were using an older version of Spring 3. All tests were working back then, and the @DirtiesContext thing was not necessary.

Now, what worries me the most, is that I can't immediately think of an explanation for this weird behaviour. It almost seems that Springs context is dirtied, simply by launching a test which uses @Autowired beans. Not all tests are using Mocks, so it can't be that. Does this sound familiar to anyone? Has anyone had the same experiences with integration testing with (the latest version of) Spring?

On Stackoverflow, I've found this ticket: How can a test 'dirty' a spring application context? It seems to pretty much sum up the behaviour I'm seeing, but the point is that we're autowiring services/repositories/..., and that we don't have any setters on those classes whatsoever.

Any thoughts?

Thanks!

like image 208
gjoris Avatar asked May 30 '13 09:05

gjoris


People also ask

What is dirty context?

@DirtiesContext is a Spring testing annotation. It indicates the associated test or class modifies the ApplicationContext. It tells the testing framework to close and recreate the context for later tests. We can annotate a test method or an entire class.

Should I use DirtiesContext?

You are better using it only on tests that you absolutely positively need it. Execution speed will be far too slow if you use @DirtiesContext on every test and you won't be getting anything in return.

How can you access the application context in a Spring integration test?

By default the ApplicationContext is loaded using the GenericXmlContextLoader which loads a context from XML Spring configuration files. You can then access beans from the ApplicationContext by annotating fields in your test class with @Autowired , @Resource , or @Inject .

What does SpringBootTest annotation do?

The @SpringBootTest annotation loads the complete Spring application context. In contrast, a test slice annotation only loads beans required to test a particular layer. And because of this, we can avoid unnecessary mocking and side effects.


1 Answers

To answer my own question, the secret was in the Spring version. We were using Spring 3.1.3, whereas I presumed they were using Spring 3.2 (they were constantly speaking about a recent upgrade of the Spring version).

The explanation was here, a blog post I stumbled over in my hunt to get it fixed: Spring Framework 3.2 RC1: New Testing Features

And a copy paste of the relevant piece:

The use of generic factory methods in Spring configuration is by no means specific to testing, but generic factory methods such as EasyMock.createMock(MyService.class) or Mockito.mock(MyService.class) are often used to create dynamic mocks for Spring beans in a test application context. For example, prior to Spring Framework 3.2 the following configuration could fail to autowire the OrderRepository into the OrderService. The reason is that, depending on the order in which beans are initialized in the application context, Spring would potentially infer the type of the orderRepository bean to be java.lang.Object instead of com.example.repository.OrderRepository.

So, how did I solve this problem? Well, I did the following steps:

  • create a new maven module
  • filter out the tests which needed mocking. All the non-mocked test would run normallly in a Spring build, in a separate Failsafe run (I created a base-package "clean", and sorted them out like that)
  • Put all the mocked tests in a base package called "mocked", and make an additional run in Failsafe for the mocked tests.
  • Each mocked test is using Springockito, to create the mocks. I'm also using the Springockito annotations, to easily do a @ReplaceWithMock in place. Every mocked test is then annotated with @DirtiesContext, so the context is dirtied after each test, and the Spring context is reintroduced with each test.

The only reasonable explanation that I could give, is that the context is effectively being dirtied, because there is a framework (Springockito) which is taking over the management of the Spring beans from the Spring framework. I don't know if that's correct, but it's the best explanation I could come up with. That, in fact, is the definition of a dirty context, which is why we need to flag it as dirty.

Using this strategy, I got the build up and running again, and all tests are running ok. It's not perfect, but it's working, and it's consistent.

like image 82
gjoris Avatar answered Sep 27 '22 20:09

gjoris