Announcement Announcement Module
Collapse
No announcement yet.
Question about Dependency Injection in Tests Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Question about Dependency Injection in Tests

    Hi everyone,

    I have a question about Dependency Injection in a Unit Test through Spring.

    I had a big problem.
    I have a Unit Test that extends AbstractDependencyInjectionSpringContextTests. In the test, i have a private member that refers to an interface TrackService. Of this interface i have an implementation TrackServiceImpl.

    I've overrided the getConfigLocations method to load the necessary files, and i've set up my applicationContext-*-test.xml files.

    I had the following setup:

    applicationContext-tests-test.xml
    Code:
    <beans>
    	<bean id="trackServiceTest" class="be.dolmen.cdlib.services.TrackServiceTest">
    		<property name="trackService">
    			<ref bean="trackService" />
    		</property>
    	</bean>
        </beans>
    applicationContext-service-test.xml
    Code:
    <bean id="trackServiceTarget" class="be.dolmen.cdlib.services.impl.TrackServiceImpl"/>
    	
    <bean id="trackService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
            <property name="transactionManager">
            	<ref bean="transactionManager"/>
            </property>
            <property name="target">
            	<ref local="trackServiceTarget"/>
            </property>
            <property name="transactionAttributeSource">
            	<ref local="defaultTxAttributes"/>
            </property>
        </bean>
    There are some other beans, like a sessionFactory, and the transactionManager, but i don't think they matter in this question.
    I'll post them later if needed.

    Well ... i got the DependencyInjectionException or something like that. Spring couldn't inject the trackService into my test, because there were two beans of type interface TrackService, and he didn't know which one to set. (AbstractDependencyInjectionSpringContextTests defaults auto-wiring byType ... couldn't figure out how to disable it, except in my setup method of my test, but the test isn't even called yet).

    Now, i finally solved it, by reading the Spring reference concerning tests, which stated that when using two beans of the same type, you couldn't use dependency injection.


    But now i'm baffeled...Where am i using those two same types?
    I have ONE bean trackServiceTarget of type TrackServiceImpl, and ONE bean trackService of type TransactionProxyFactoryBean.

    To me, those are two different things (and i would like to work with the interface in my test too).

    So, why a question when you solved it??

    The question is Why ...
    Is it because my trackService is a TransactionPROXYFactoryBean , that perhaps refers to the interface TrackService, as a proxy, that Spring Thinks i have two times the same bean?
    Or why does Spring say i have to beans of the same type (while this isn't true ... I commented everything in my applicationContext files, in production as in test, except the things i really needed for this test, and i only have ONE bean of type TrackService (even TrackServiceImpl which implements the interface, and not the interface itself).

    Why is this happening? Please broaden my understanding of this problem, even while it is solved already.

  • #2
    Hi Don

    You have already hit the nail on the head when you write...

    I have ONE bean trackServiceTarget of type TrackServiceImpl, and ONE bean trackService of type TransactionProxyFactoryBean.
    When the Spring container is finished starting up (i.e. it is totally initialized and has preinstantiated all of the singleton bean definitions), what it has is TWO TrackService instances... the first is the TrackServiceImpl that you are declaring explicitly, and the seocnd is the TrackService proxy.

    There are (three as I see it) solutions to your issue (yeah, I know you already solved it, I'm just trying to be explicit). The first is to NOT declare your TrackServiceImpl bean as a top level bean, but rather as an inner bean of the TransactionProxyFactoryBean's target property; to wit...

    Code:
    <bean id="trackService" class="org...TransactionProxyFactoryBean">
        <property name="target">
            <bean class="...TrackServiceImpl">
                <!-- inject other dependecies and configuration as required here -->
            </bean>
        </property>
    </bean>
    The second is to switch from using autowire-by-type to using autowire-by-name in your TrackServiceTest class. This can be accomplished by overriding one or both of the superclass constructors and calling the inherited setAutowireMode(AbstractDependencyInjectionSpringC ontextTests.AUTOWIRE_BY_NAME) method (with that constant value). You will then have to have a write property with the same name as the TrackService instance that you want to inject.

    Your third option is possibly the simplest... don't dependency inject your test, but rather invoke getBean("trackService") explicitly. If you are inheriting from AbstractDependencyInjectionSpringContextTests, you will have access to a protected instance variable called applicationContext, and you can pull beans from it using super.applicationContext.getBean("...").

    I hope that clears things up a little for you. Feel free to ping back on the forum if not :|

    Before I go, a (small) thing in closing. The JUnit support classes that Spring provides are really only for integration testing, and not unit testing. For unit testing, the Spring team recommends just mocking or stubbing out your collaborators and 'injecting' them manually via explicit Java calls to setter methods and suchlike.

    Cheers
    Rick

    Comment


    • #3
      Hi,

      Thanks for your quick reply.
      So, as i understand, it is indeed because i use a proxy, which will be an instance of TrackService ... why Spring thinks i have two beans of type TrackService.

      I tried your first option, and it worked. But i would like to make the bean a ref-bean because i might reuse it in other tests. (In production, this is a ref-bean, because it will be used in multiple struts-actions).

      I'll try your second option, but my tests weren't called yet. But perhaps it will be when i override the constructor for that test. I tried to set the auto-wire to byName by calling the setter in my test method, but that didn't succeed. I also tried typing the auto-wire="byName" in the applicationContext.xml files in all three beans, but that didn't succeed.

      Now, i am using your third option.
      I have made a ServiceTestCase class which extends AbstractDependencyInjectionSpringContextTests, with the following code:

      Code:
      private String[] paths = { "applicationContext*test.xml" };
      
          /**
           * @see org.springframework.test.AbstractDependencyInjectionSpringContextTests#getConfigLocations()
           */
          protected String[] getConfigLocations() {
              return paths;
          }
      
          /**
           * Sets the path relative to the classpath of the Spring configuration files
           * used for unit testing
           * 
           * @param paths
           *            paths
           */
          protected void setApplicationContextPath(String[] paths) {
              this.paths = paths;
          }
      My TrackServiceTest, which extends ServiceTestCase, is implemented as follows:
      Code:
      private TrackService trackService;
      
          /**
           * @see be.dolmen.cdlib.services.ServiceTestCase#onSetUp()
           */
          protected void onSetUp() throws Exception {
              super.onSetUp();
              trackService = (TrackService) applicationContext
                      .getBean("trackService");
          }
      
          /**
           * @see be.dolmen.cdlib.services.ServiceTestCase#onTearDown()
           */
          protected void onTearDown() throws Exception {
              super.onTearDown();
              trackService = null;
          }
      
          /**
           * @throws Exception
           *             When an Exception occurs in this test.
           */
          public void testTrackServiceInjection() throws Exception {
              assertNotNull(trackService);
          }
      I would've liked to use Spring as much as possible, to inject my services in my unit tests, where i would like to test the service (One per unit test).
      But, perhaps you're right, and i shouldn't be testing dependency injection in my unit tests but use "trackService = new TrackServiceImpl();" in the onSetup method of my unit test, instead of injecting it.
      And just test it that way, instead of letting Spring handle creation and injection of the service into my unit test.

      What do you think?
      Thank you very much

      Comment


      • #4
        Hi Don

        In production, this is a ref-bean, because it will be used in multiple struts-actions.
        I'll bite... surely you would only ever want to inject a transactional proxy for your TrackService into your Struts Actions? I mean, will it ever make sense to inject the plain vanilla POJO TrackServiceImpl into your Struts Actions?

        With regard to unit tests, (and this is just my personal opinion), if I was writing the unit tests for the TrackServiceImpl, I would simply create a JUnit class called TrackServiceImplTests that extended the basic JUnit TestCase class, and I would inject any dependencies of the TrackServiceImpl manually (i.e. just using plain old Java code). This allows me to get up to speed and get the basics of the TrackServiceImpl working before needing to involve something like Spring. To wit (I apologise in advance if I am doing the whole granny and eggs thing)...

        Code:
        public final class TrackServiceImplTests extends TestCase {
        
           public void testThisScenario() {
              
              TrackServiceImpl trackService = new TrackServiceImpl();
              
              // inject dependencies (collaborators) here, 'manually'
              
              trackService.setFoo(new StubFoo());
           }
        
           public void testThatScenario() {
              
              TrackServiceImpl trackService = new TrackServiceImpl();
              
              // inject dependencies (collaborators) here, 'manually'
              
              trackService.setFoo(new StubFoo());
           }
        }
        Once it came time to actually test the TrackServiceImpl working in concert with all of the other pieces of my application (i.e. other classes that would actually connect to a database, publish messages to queues, etc), then I would start involving Spring because the Spring IoC container is darn good at wiring objects together. I see that you have created a superclass (ServiceTestCase) to simplify the writing of your integration test classes. This is fine if you need it, and I have certainly seen this idiom before.

        Finally, I just noticed that you have a file called 'applicationContext-tests-test.xml' that contains a bean definition for your TrackServiceTest class. You don't actually need this file or indeed this bean definition. You don't normally want to define your JUnit test classes as beans, because then Spring will be instantiating your tests for you, and then JUnit will be doing so too, and that well, that's just not what one typically wants. I think you;re safe to go ahead and delete that file

        Cheers
        Rick

        Comment

        Working...
        X