Announcement Announcement Module
Collapse
No announcement yet.
Integration Testing using a Mock and Latch works but not with other JUnits Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Integration Testing using a Mock and Latch works but not with other JUnits

    I'm facing a frustrating issue when running a JUnit that tests Spring Integration that includes asynchronous processing.

    To help make this easier I'm using a CountDownLatch to recognise when a point in the asynchronous processing occurs.

    This is similar to the asynchronous testing strategy in 'Spring Integration in Action' book and similar found at
    http://blog3.xebia.com/2009/11/03/la...current-tests/

    In order for this to work I place the 'latch' into a endpoint that's been mocked with Mockito. All works fine if the JUnit runs on it's own :-

    - the Junit main thread sends a message in
    - at somepoint the processing goes asynchronous (due to a JMS backed channel, using embedded activemq)
    - the asynchronous thread receives the message and eventually the mocked endpoint gets called, and the latch gets decremented.
    - meanwhile, the main junit thread continues and calls latch.await(10000, MILLESECONDS) and only moves on once the count down gets to zero (or times out)
    - assertions are then made

    So this all runs fine on it's own, but if this runs along with other JUnits then the latch never gets counted down.

    The reason I thought was because the Spring Context gets cached across JUnits, meaning that the real endpoint gets injected - rather than the 'mock' endpoint that counts down the latch.

    Thinking along these lines, I added a test method to the top of the JUnit that does nothing aside from declaring @DirtiesContext

    thinking this would force the real endpoint out of the Spring Context, and allow the subsequent testmethod to reinitialise the context - which it seems to, and then hopefully load the mock endpoint.

    However, the problem remains - my guess is that although @DirtiesContext rebuilds the context, there is probably a 'defaultMessageListenerContainer' thread that is still running from a previous JUnit, and that this thread remains and ends up being the consumer of the message, and is still using the real endpoint.


    I hit this problem a few months ago, and so now generally I try to avoid this kind of JUnit test and instead test the asynchronous part in isolation to avoid this problem. But there's a small number of cases where we really need to test like this.

    Assuming my reasoning for it not working is correct:-

    - is there anyway to force the threads to reinitialise before the test method ? or at the @DirtiesContext point (aside from forking every test)
    - has anyone else hit this problem, I thought this would be common problem

  • #2
    This could be due to fast cycling of embeddded ActiveMQ ("vm://localhost?broker.persistent=false") - it can cause issues - e.g. a send() might go to the old instance that is in the process of shutting down and the receive() will never get a message from the new instance.

    The instance goes down when all connections are closed - the issue is that even with @DirtiesContext, the closing of the connection in the DMLC is asynchronous and so your next test might hit the above problem - starting before the old instance went down.

    We recently hit this in the framework tests themselves (SubscribableJmsChannelTests); the solution was to explicitly close the connection after each test - forcing the vm:// instance to go away before the next test starts.

    This solved the problem for us, but it might make sense to sleep for a while just to be sure.

    Code:
           @After
           public void tearDown() throws Exception {
                   this.connectionFactory.resetConnection();
           }

    Comment


    • #3
      Thanks for your reply Gary.

      I looked at implementing a similar workaround that you have used, but found that we weren't using CachingConnectionFactory so there's was no resetConnection() available to call. However I changed to use CachingConnectionFactory wrapping our connectionFactory as follows :-


      Code:
      	  
             <bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
      		<property name="targetConnectionFactory">
      			<bean class="org.apache.activemq.ActiveMQConnectionFactory">
      				<property name="brokerURL" value="vm://localhost?broker.persistent=false"/>		
      			</bean>
      		</property>
      		<property name="sessionCacheSize" value="3"/>
      		<property name="cacheProducers" value="false"/>
      	</bean>
      with

      Code:
      	
              @After
      	public void tearDown() throws Exception {
      
      		System.out.println("running tearDown()");
      
      		System.out.println("resetConnection().... ");
      		this.connectionFactory.resetConnection();
      
      		System.out.println("finished resetConnection() ");
      	}
      but no change. Also I tried adding a 20 second sleep before and after each test method, but again no change.

      It seems that the DMLC threads are already present and presumably hold a reference to the real endpoint rather than the mock.

      Maybe what I need is a way to kill all theads except the main JUnit thread.

      Any more thoughts from anyone - much appreciated.

      Comment


      • #4
        I added a test method to the top of the JUnit that does nothing aside from declaring @DirtiesContext
        I misread this the first time - try setting it on ALL test methods - or annotate the entire class like so...

        Code:
        @DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD)
        (I assume you already have @ContextConfiguration and @Runwith(SpringJUnit4ClassRunner.class)).

        Comment


        • #5
          I tried the
          @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD
          same issue unfortunately.

          Yes I have SpringJUnit4ClassRunner and @ContextConfiguration :-
          Code:
          @RunWith(SpringJUnit4ClassRunner.class)
          @ContextConfiguration( 
          inheritLocations = false, 
          value = { 
          "classpath:/META-INF/test-service-context.xml", 
          "classpath:/META-INF/integration/comms/test-mock-for-webmethods-outbound-adapter.xml" } 
          )
          @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
          I know the mock is defined correctly as the JUnit works on it's own
          Code:
          	
                   <bean id="secureCommsPublishWebmethodsOutboundChannelAdapter" 
          			class="org.mockito.Mockito" factory-method="mock">
          	    <constructor-arg value="org.springframework.integration.core.MessageHandler" />
          	</bean>
          I'm not sure that ActiveMQ itself is the issue, as I have also hit the same problem with a slightly different recipe for testing async processing:- where the JUnit sends a message to a JMS backed channel, but instead of using a latch, the JUnit's Spring Context overrides a synchronous Channel with a QueueChannel. Then the JUnit knows the message will stop when it reaches the queueChannel, and the JUnit can call receive() with a timeout to wait for the async processing to finish. Again we've found this works perfectly when a Test is run on it's own, but after other tests it gets the same issue - the override does not take effect, as the DMLC thread is already existing and is long-lived ?
          This alternative approach is also mentioned in 'Spring Integration in Action' book

          Override with Queue approach :-
          Code:
          	
          <!-- overrides to use a queue, so that Junit can call receive(10000);  -->
           	<int:channel id="ackPublish">
          		<int:queue/>
          	</int:channel>
          Any ideas how I can force the shutdown of DMLC threads ?

          For info, using the first approach, I know the mock endpoint is not being used, as I can debug with a breakpoint in the DMLC thread (during a test run where a set of JUnits have already executed) and see that the real endpoint is referenced rather than the mock.

          The mocked endpoint should be used as it has the same bean id, and comes from the Spring Context that is listed last in the JUnit's @ContextConfiguration

          Comment


          • #6
            Hmmm... there must be something else going on. I just ran a quick test and you can see from the attached log that the container (and all the listener threads) is completely shut down between the tests...

            Code:
            @ContextConfiguration
            @RunWith(SpringJUnit4ClassRunner.class)
            @DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD)
            public class FooTest {
            
            	@Test
            	public void test1() throws Exception {
            		Thread.sleep(5000);
            		System.out.println("Test1");
            	}
            
            	@Test
            	public void test2() throws Exception {
            		Thread.sleep(5000);
            		System.out.println("Test2");
            	}
            }
            
            <int-jms:message-driven-channel-adapter channel="foo" connection-factory="ccf" destination-name="foo" />
            	
            <int:channel id="foo">
            	<int:queue/>
            </int:channel>
            	
            <bean id="ccf" class="org.springframework.jms.connection.CachingConnectionFactory">
            	<property name="targetConnectionFactory" ref="connectionFactory" />
            </bean>
            	
            <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
            	<property name="brokerURL" value="vm://localhost?broker.persistent=false" />
            </bean>
            Perhaps you can run with trace logging and compare your logs with this one?

            Comment

            Working...
            X