views:

2677

answers:

9

Hi all,

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.

+1  A: 

Easy. You use a custom application context for your unit tests. Or you don't use one at all and you manually create and inject your beans.

It sounds to me like your testing might be a bit too broad. Unit testing is about testing, well, units. A Spring bean is a pretty good example of a unit. You shouldn't need an entire application context for that. I find that if your unit testing is so high level that you need hundreds of beans, database connections, etc then you have a really fragile unit test that is going to break on the very next change, will be hard to maintain and really isn't adding a lot of value.

cletus
You're right, we are using rather system testing and not unit testing, we actually test the flow from the API to the DB on our application server. I realize writing unit test is a better way, but it's not feasible at this moment.
Stas
A: 

Perhaps you could use qualifiers for your beans? You would redefine the beans you want to mock up in a separate application context and label them with a qualifier "test". In your unit tests, when wiring your beans always specify the qualifier "test" to use the mock ups.

Il-Bhima
A: 

You can use the import feature in your test app context to load in the prod beans and override the ones you want. For example, my prod data source is usually acquired via JNDI lookup, but when I test I use a DriverManager data source so I don't have to start the app server to test.

duffymo
+3  A: 

You can also write your unit tests to not require any lookups at all:

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

    @Autowired
    private MyBean myBean; // the component under test

    @Test
    public void testMyBean() {
        ...
    }
}

This gives an easy way to mix and match real config files with test config files.

For example, when using hibernate, I might have my sessionFactory bean in one config file (to be used in both the tests and the main app), and have by dataSource bean in another config file (one might use a DriverManagerDataSource to an in-memory db, the other might use a JNDI-lookup).

But, definitely take heed of @cletus's warning ;-)

toolkit
this would lead to many duplicate bean.xml for the tests, for mock-only test i'd rather propose easyMock or similiar mock frameworks
Michael Lange
Yep - some of our tests used EasyMock instead of this approach. IMHO EasyMock tends to shine for real unit tests. More 'integration-like' tests benefit from the ContextConfiguration approach.
toolkit
+3  A: 

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 in the "normal" locations - your normal locations might differ

but... i would never mix mock and non-mock beans it's hard to trace problems, when the application grows older

Michael Lange
+4  A: 

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.

krosenvold
Just curious, doesn't that lead to some maintenance hell regarding individual @ContextConfiguration per Testclass ?
Michael Lange
No, we only have 5 system-wide @ConfigurationContxts *totally* in a large application. I re-edited to make this clearer.
krosenvold
Thanks for this answer, I applied it with success. One thing to note is that this way of doing is very flexible because you can use several context:component-scan tags (e.g. one per module or per package). Also, it may help to set 'use-default-filters="false".
Gaël Marziou
A: 

I want to do the same thing, and we're finding it essential.

The current mechanism we use is fairly manual but it works.

Say for instance, you wish to mock out bean of type Y. What we do is every bean that has that dependency we make implement an interface - "IHasY". This interface is

interface IHasY { public void setY(Y y); }

Then in our test we call the util method...

 public static void insertMock(Y y) {
        Map invokers = BeanFactory.getInstance().getFactory("core").getBeansOfType(IHasY.class);
        for (Iterator iterator = invokers.values().iterator(); iterator.hasNext();) {
            IHasY invoker = (IHasY) iterator.next();
            invoker.setY(y);
        }
    }

I do not want to create a whole xml file just to inject this new dependency and that is why I like this.

If you're willing to create an xml config file then the way to go would be to create a new factory with the mock beans and make your default factory a parent of this factory. Make sure then that you load all your beans from the new child factory. When doing this the sub-factory will override the beans in the parent factory when the bean id's are the same.

Now if, in my test, If I could programmatically create a factory, that would be awesome. Having to use xml is just too cumbersome. I'm looking to create that child factory with code. Then each test can configure its factory the way it wants. There's no reason why a factory like that won't work.

Michael Wiles
A: 

There are some very complicated and powerful solutions listed here.

But there is a FAR, FAR simpler way to accomplish what Stas has asked, which doesn't involve modifying anything other than one line of code in the test method. It works for unit tests and Spring integration tests alike, for autowired dependencies, private and protected fields.

Here it is:

junitx.util.PrivateAccessor.setField(testSubject, "fieldName", mockObject);
Daniel Alexiuc
A: 

I don't have the reputation points to pile on duffymo's answer, but I just wanted to chime in and say his was the "right" answer for me.

Instantiate a FileSystemXmlApplicationContext in your unit test's setup with a custom applicationContext.xml. In that custom xml, at the top, do an as duffymo indicates. Then declare your mock beans, non-JNDI data sources, etc, that will override the id's declared in the import.

Worked like a dream for me.