Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring beans redefinition in unit test environment

We are using Spring for my application purposes, and Spring Testing framework for unit tests. We have a small problem though: the application code loads a Spring application context from a list of locations (XML files) in the classpath. But when we run our unit tests, we want some of the Spring beans to be mocks instead of full-fledged implementation classes. Moreover, for some unit tests we want some beans to become mocks, while for other unit tests we want other beans to become mocks, as we are testing different layers of the application.

All this means I want to redefine specific beans of the application context and refresh the context when desired. While doing this, I want to redefine only a small portion of the beans located in one (or several) original XML beans definition file. I cannot find an easy way to do it. It's always regarded that Spring is a unit-testing-friendly framework, so I must be missing something here.

Do you have any ideas how to do it?

Thanks!

like image 252
Stas Avatar asked Feb 19 '09 13:02

Stas


2 Answers

I would propose a custom TestClass and some easy rules for the locations of the spring bean.xml:

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {     "classpath*:spring/*.xml",     "classpath*:spring/persistence/*.xml",     "classpath*:spring/mock/*.xml"}) @Transactional @TestExecutionListeners({     DependencyInjectionTestExecutionListener.class,     TransactionalTestExecutionListener.class,     DirtiesContextTestExecutionListener.class}) public abstract class AbstractHibernateTests implements ApplicationContextAware {      /**      * Logger for Subclasses.      */     protected final Logger log = LoggerFactory.getLogger(getClass());      /**      * The {@link ApplicationContext} that was injected into this test instance      * via {@link #setApplicationContext(ApplicationContext)}.      */     protected ApplicationContext applicationContext;      /**      * Set the {@link ApplicationContext} to be used by this test instance,      * provided via {@link ApplicationContextAware} semantics.      */     @Override     public final void setApplicationContext(             final ApplicationContext applicationContext) {         this.applicationContext = applicationContext;     } } 

If there are mock-bean.xml in the specified location, they will override all "real" bean.xml files in the "normal" locations - your normal locations might differ.

But … I would never mix mock and non-mock beans, as it's hard to trace problems when the application grows older.

like image 50
Michael Pralow Avatar answered Sep 23 '22 15:09

Michael Pralow


One of the reasons spring is described as test-friendly is because it may be easy to just new or mock stuff in the unit test.

Alternately we have used the following setup with great success, and I think it is quite close to what you want, I would strongly recommend it:

For all beans that need different implementations in different contexts, switch to annotation based wiring. You can leave the others as-is.

Implement the following set of annotations

 <context:component-scan base-package="com.foobar">      <context:include-filter type="annotation" expression="com.foobar.annotations.StubRepository"/>      <context:include-filter type="annotation" expression="com.foobar.annotations.TestScopedComponent"/>      <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>  </context:component-scan> 

Then you annotate your live implementations with @Repository, your stub implementations with @StubRepository, any code that should be present in the unit-test fixture ONLY with @TestScopedComponent. You may run into needing a couple more annotations, but these are a great start.

If you have a lot of spring.xml, you will probably need to make a few new spring xml files that basically only contain the component-scan definitions. You'd normally just append these files to your regular @ContextConfiguration list. The reason for this is because you frequently end up with different configurations of the context-scans (trust me, you will make at least 1 more annotations if you're doing web-tests, which makes for 4 relevant combinations)

Then you basically use the

@ContextConfiguration(locations = { "classpath:/path/to/root-config.xml" }) @RunWith(SpringJUnit4ClassRunner.class) 

Note that this setup does not allow you to have alternating combinations of stub/live data. We tried this, and I think that resulted in a mess I wouldn't recommend anyone ;) We either wire inn the full set of stubs or the full set of live services.

We mainly use auto-wired stub dependencies when testing gui near stuff where the dependencies are usually quite substantial. In cleaner areas of the code we use more regular unit-testing.

In our system we have the following xml-files for component-scan:

  • for regular web production
  • for starting web with stubs only
  • for integration tests (in junit)
  • for unit tests (in junit)
  • for selenium web tests (in junit)

This means we totally have 5 different system-wide configurations that we can start the application with. Since we only use annotations, spring is fast enough to autowire even those unit tests we want wired. I know this is untraditional, but it's really great.

Out integration tests run with full live setup, and once or twice I have decided to get really pragmatic and want to have a 5 live wirings and a single mock:

public class HybridTest {    @Autowired    MyTestSubject myTestSubject;      @Test    public void testWith5LiveServicesAndOneMock(){      MyServiceLive service = myTestSubject.getMyService();      try {           MyService mock = EasyMock.create(...)           myTestSubject.setMyService( mock);             .. do funky test  with lots of live but one mock object       } finally {           myTestSubject.setMyService( service);      }      } } 

I know the test purists are going to be all over me for this. But sometimes it's just a very pragmatic solution that turns out to be very elegant when the alternative would be really really ugly. Again it's usually in those gui-near areas.

like image 32
krosenvold Avatar answered Sep 21 '22 15:09

krosenvold