`

Mocking JMS infrastructure with MockRunner to favour testing

    博客分类:
  • JMS
 
阅读更多

From Marco Tedone (http://tedone.typepad.com/blog/2011/07/mocking-spring-jms-with-mockrunner.html)

 

This article shows *one* way to mock the JMS infrastructure in a Spring JMS application. This allows us to test our JMS infrastructure without actually having to depend on a physical connection being available. If you are reading this article, chances are that you are also frustrated with failing tests in your continuous integration environment due to a JMS server being (temporarily) unavailable. By mocking the JMS provider, developers are left free to test not only the functionality of their API (unit tests) but also the plumbing of the different components, e.g. in a Spring container.

In this article I show how a Spring JMS Hello World application can be fully tested without the need of a physical JMS connection. I would like to stress the fact that the code in this article is by no means meant for production and that the approach shown is just one of many.

The infrastructure

For this article I use the following infrastructure:

  • Apache ActiveMQ, an open source JMS provider, running on an Ubuntu installation
  • Spring 3
  • Java 6
  • MockRunner
  • Eclipse as development environment, running on Windows 7

The Spring configuration

It's my belief that using what I define as Spring Configuration Strategy Pattern (SCSP) is the right solution in almost all cases when there is the need for a sound testing infrastructure. I will dedicate an entire article to SCSP, for now this is how it looks:

The Spring application context

Here follows the content of jemosJms-appContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jms="http://www.springframework.org/schema/jms"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">    
    
    <bean id="helloWorldConsumer" class="uk.co.jemos.experiments.HelloWorldHandler" />
    
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
      <property name="connectionFactory" ref="jmsConnectionFactory" />
    </bean>

    <jms:listener-container connection-factory="jmsConnectionFactory" >
        <jms:listener destination="jemos.tests" ref="helloWorldConsumer" method="handleHelloWorld" />
    </jms:listener-container>

</beans>

The only important thing to note here is that there are some services which rely on an existing bean named jmsConnectionFactory but that such bean is not defined in this file. This is key to the SCSP and I will illustrate this in one of my future articles.

The Spring application context implementation

Here follows the content of jemosJms-appContextImpl.xml which could be seen as an implementation of the Spring application context defined above

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jms="http://www.springframework.org/schema/jms"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">

    <import resource="classpath:jemosJms-appContext.xml" />

    <bean id="jmsConnectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory">
      <property name="brokerURL" value="tcp://myJmsServer:61616" />
    </bean>

</beans>

This Spring context file imports the Spring application context defined above and it is this application context which declared the connection factory.

This decoupling of the bean requirement (in the super context) from its actual declaration (Spring application context implementation) represents the cornerstore of SCSP.

Mocking the JMS provider - The Spring Test application context and MockRunner

Following the same approach I used above, I can now declare a fake connection factory which does not require a physical connection to a JMS provider. Here follows the content of jemosJmsTest-appContext.xml. Please note that this file should reside in the test resources of your project, i.e. it should never make it to production.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jms="http://www.springframework.org/schema/jms"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">

    <import resource="classpath:jemosJms-appContext.xml" />

    <bean id="destinationManager" class="com.mockrunner.jms.DestinationManager"/>
    <bean id="configurationManager" class="com.mockrunner.jms.ConfigurationManager"/>


    <bean id="jmsConnectionFactory" class="com.mockrunner.mock.jms.MockQueueConnectionFactory" >
        <constructor-arg index="0" ref="destinationManager" />
        <constructor-arg index="1" ref="configurationManager" />
    </bean>

</beans>

Here the Spring test application context file imports the Spring application context (not its implementation) and it declares a fake connection factory, thanks to the MockRunnerMockQueueConnectionFactory class.

A POJO listener

The job of handling the message is delegated to a simple POJO, which happens to be declared also as a bean:

package uk.co.jemos.experiments;

public class HelloWorldHandler {

    /** The application logger */
    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
            .getLogger(HelloWorldHandler.class);

    public void handleHelloWorld(String msg) {

        LOG.info("Received message: " + msg);

    }

}

There is nothing glamorous about this class. In real life this should have probably be the implementation of an interface, but here I wanted to keep things simple.

A simple JMS message producer

Here follows an example of a JMS message producer, which would use the real JMS infrastructure to send messages:

package uk.co.jemos.experiments;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;

public class JmsTest {

    /** The application logger */
    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
            .getLogger(JmsTest.class);

    /**
     * @param args
     */
    public static void main(String[] args) {

        ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "classpath:jemosJms-appContextImpl.xml");

        JmsTemplate jmsTemplate = ctx.getBean(JmsTemplate.class);

        jmsTemplate.send("jemos.tests", new HelloWorldMessageCreator());

        LOG.info("Message sent successfully");

    }
    
}

The only thing of interest here is that this class retrieves the real JmsTemplate to send a message to the queue.

Now if I was to run this class as is, I would obtain the following:

2011-07-31 17:09:46 ClassPathXmlApplicationContext [INFO] Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@19e0ff2f: startup date [Sun Jul 31 17:09:46 BST 2011]; root of context hierarchy
2011-07-31 17:09:46 XmlBeanDefinitionReader [INFO] Loading XML bean definitions from class path resource [jemosJms-appContextImpl.xml]
2011-07-31 17:09:46 XmlBeanDefinitionReader [INFO] Loading XML bean definitions from class path resource [jemosJms-appContext.xml]
2011-07-31 17:09:46 DefaultListableBeanFactory [INFO] Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@3479e304: defining beans [helloWorldConsumer,jmsTemplate,org.springframework.jms.listener.DefaultMessageListenerContainer#0,jmsConnectionFactory]; root of factory hierarchy
2011-07-31 17:09:46 DefaultLifecycleProcessor [INFO] Starting beans in phase 2147483647
2011-07-31 17:09:47 HelloWorldHandler [INFO] Received message: Hello World
2011-07-31 17:09:47 JmsTest [INFO] Message sent successfully

Writing the integration test

There are various interpretations as to what different types of tests mean and I don't pretend to have the only answer; my interpreation is that an integration test is a functional test which also wires up different components together but which does not interact with real external infrastructure (e.g. a Dao integration test fakes data, a JMS integration test fakes the JMS physical connection, an HTTP integration test fakes the remote Web host, etc). Whereas in my opinion, the main purpose of a unit (aka functional) test is to let the API emerge from the tests, the main goal of an integration test is to test that the plumbing amongst components works as expected so as to avoid surprises in a production environment.

Both unit (functional) and integration tests should run very fast (e.g. under 10 minutes) as they constitute what can be considered the "development token". If unit and integration tests are green one should feel pretty confident that 90% of the functionality works as expected; in my projects when both unit and integration tests are green I let developers free to release the token. This does not mean that the other 10% (e.g. the interaction with the real infrastructure) should not be tested, but this can be delegated to system tests which run nightly and don't require the development token. Because unit and integration tests need to run fast, interaction with external infrastructure should be mocked whenever possible.

Here follows an integration test for the Hello World handler:

package uk.co.jemos.experiments.test.integration;

import javax.annotation.Resource;
import javax.jms.TextMessage;

import junit.framework.Assert;

import org.junit.Before;
import org.junit.Test;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;

import uk.co.jemos.experiments.HelloWorldHandler;
import uk.co.jemos.experiments.HelloWorldMessageCreator;

import com.mockrunner.jms.DestinationManager;
import com.mockrunner.mock.jms.MockQueue;


/**
 * @author mtedone
 * 
 */
@ContextConfiguration(locations = { "classpath:jemosJmsTest-appContextImpl.xml" })
public class HelloWorldHandlerIntegrationTest extends AbstractJUnit4SpringContextTests {

    @Resource
    private JmsTemplate jmsTemplate;

    @Resource
    private DestinationManager mockDestinationManager;

    @Resource
    private HelloWorldHandler helloWorldHandler;

    @Before
    public void init() {
        Assert.assertNotNull(jmsTemplate);
        Assert.assertNotNull(mockDestinationManager);
        Assert.assertNotNull(helloWorldHandler);
    }

    @Test
    public void helloWorld() throws Exception {
        MockQueue mockQueue = mockDestinationManager.createQueue("jemos.tests");

        jmsTemplate.send(mockQueue, new HelloWorldMessageCreator());

        TextMessage message = (TextMessage) jmsTemplate.receive(mockQueue);

        Assert.assertNotNull("The text message cannot be null!",
                message.getText());

        helloWorldHandler.handleHelloWorld(message.getText());

    }

}

And here follows the output:

2011-07-31 17:17:26 XmlBeanDefinitionReader [INFO] Loading XML bean definitions from class path resource [jemosJmsTest-appContextImpl.xml]
2011-07-31 17:17:26 XmlBeanDefinitionReader [INFO] Loading XML bean definitions from class path resource [jemosJms-appContext.xml]
2011-07-31 17:17:26 GenericApplicationContext [INFO] Refreshingorg.springframework.context.support.GenericApplicationContext@f01a1e: startup date [Sun Jul 31 17:17:26 BST 2011]; root of context hierarchy
2011-07-31 17:17:27 DefaultListableBeanFactory [INFO] Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@39478a43: defining beans [helloWorldConsumer,jmsTemplate,org.springframework.jms.listener.DefaultMessageListenerContainer#0,destinationManager,configurationManager,jmsConnectionFactory,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor]; root of factory hierarchy
2011-07-31 17:17:27 DefaultLifecycleProcessor [INFO] Starting beans in phase 2147483647
2011-07-31 17:17:27 HelloWorldHandler [INFO] Received message: Hello World
2011-07-31 17:17:27 GenericApplicationContext [INFO] Closingorg.springframework.context.support.GenericApplicationContext@f01a1e: startup date [Sun Jul 31 17:17:26 BST 2011]; root of context hierarchy
2011-07-31 17:17:27 DefaultLifecycleProcessor [INFO] Stopping beans in phase 2147483647
2011-07-31 17:17:32 DefaultMessageListenerContainer [WARN] Setup of JMS message listener invoker failed for destination 'jemos.tests' - trying to recover. Cause: Queue with name jemos.tests not found
2011-07-31 17:17:32 DefaultListableBeanFactory [INFO] Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@39478a43: defining beans [helloWorldConsumer,jmsTemplate,org.springframework.jms.listener.DefaultMessageListenerContainer#0,destinationManager,configurationManager,jmsConnectionFactory,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor]; root of factory hierarchy

In this test, although we simulated a message roundtrip to a JMS queue, the message never left the current JVM and it the whole execution did not depend on a JMS infrastructure being up. This gives us the power to simulate the JMS infrastructure, to test the integration of our business components without having to fear a red from time to time due to JMS infrastructure being down or inaccessible.

Please note that in the output there are some warnings because the JMS listener container declared in the jemosJms-appContext.xml does not find a queue named "jemos.test" in the fake connection factory, but this is fine; it's a warning and does not impede the test from running successfully.

The Maven configuration

Here follows the Maven pom.xml to compile the example:

 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>uk.co.jemos.experiments</groupId>
  <artifactId>jmx-experiments</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>Jemos JMS experiments</name>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.8.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.mockrunner</groupId>
      <artifactId>mockrunner</artifactId>
      <version>0.3.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.16</version>      
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.6.1</version>      
      <scope>compile</scope>
    </dependency>    
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>1.6.1</version>      
      <scope>compile</scope>
    </dependency>    
    <dependency>
      <groupId>org.apache.activemq</groupId>
      <artifactId>activemq-all</artifactId>
      <version>5.5.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>3.0.5.RELEASE</version>      
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.0.5.RELEASE</version>      
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>3.0.5.RELEASE</version>      
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jms</artifactId>
      <version>3.0.5.RELEASE</version>      
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>3.0.5.RELEASE</version>
      <scope>test</scope>      
    </dependency>    
    
  </dependencies>
</project>

分享到:
评论

相关推荐

    Instant Mock Testing with PowerMock.pdf

    Instant Mock Testing with PowerMock 7 Saying Hello World! (Simple) 8 Getting and installing PowerMock (Simple) 14 Mocking static methods (Simple) 22 Verifying method invocation (Simple) 28 Mocking ...

    pytest-mock-1.13.0_testing_python_thin_源码.zip

    `pytest-mock-1.13.0_testing_python_thin_源码.zip`是一个包含`pytest-mock`库1.13.0版本的源代码压缩包,主要用于Python的单元测试和模拟(mocking)功能。`pytest-mock`是`pytest`框架的一个插件,它简化了Python...

    pytest-mock-1.12.1_testing_python_thin_

    testing_python_thin_`这个标题表明我们讨论的是一个与`pytest-mock`相关的版本,具体是1.12.1版,它的主要目的是为`py.test`(即pytest)提供一个轻量级的`mock`库包装器,以简化Python测试中的模拟(mocking)操作...

    Mockito Essentials(PACKT,2014)

    Whether you are new to JUnit testing and mocking or a seasoned Mockito expert, this book will provide you with the skills you need to successfully build and maintain meaningful JUnit test cases and ...

    Java.Testing.with.Spock.2016

    - **Mocking和stubbing**:Spock在处理依赖项时提供了内建的Mocking和stubbing功能,这使得单元测试中对复杂依赖进行隔离变得简单。 - **异常测试**:Spock可以非常容易地测试代码抛出异常的情况,使得错误的边界...

    Testing.with.Junit【高清PDF版】

    《Testing with Junit》这本书是Java开发者们进行单元测试的重要参考资料。Junit是Java编程语言中最广泛使用的单元测试框架,它允许程序员对代码进行自动化测试,确保代码的正确性和稳定性。下面将详细介绍Junit及其...

    Laravel开发-laravel-dusk-mocking

    在Laravel Dusk中,我们可以使用laravel的内置mocking工具,如`Mockery`或`PHPUnit`的`spy`和`mock`方法。通过mocking,我们可以确保测试只专注于正在测试的代码,而不是依赖于外部服务的正确性。例如,如果你在测试...

    SoapUI 5.1.2 crack

    Test faster and smarter with data-driven testing, increasing your API testing coverage overnight. LEARN MORE Test Reporting Need metrics, statistics and other testing data? Get detailed, ...

    Pragmatic Bookshelf Pragmatic Unit Testing in CSharp with NUnit 2nd Edition

    另一个文件"Pragmatic.Bookshelf.Pragmatic.Unit.Testing.in.C.Sharp.with.NUnit.2nd.Edition.Aug.2007.eBook-BBL"则是电子书本身,包含了详细的章节内容和深入的讨论。 总的来说,《单元测试之道C#版:使用NUnit》...

    Mockito for Spring(PACKT,2015)

    Packed with real-world examples, the book covers how to make Spring code testable and mock container and external dependencies using Mockito. You then get a walkthrough of the steps that will help ...

    Unit Testing with Microsoft Moles例子

    在"Unit Testing with Microsoft Moles"的例子中,我们将探讨如何使用Moles框架来创建和执行单元测试。Moles框架的核心理念是“桩”(stubs)和“莫尔斯”(moles),其中桩用于替代对象实例,而莫尔斯则用于替代...

    [Mock框架] Moq 4.0.10827

    Moq (pronounced "Mock-you" or just "Mock") is the only mocking library for .NET developed from scratch to take full advantage of .NET 3.5 (i.e. Linq expression trees) and C# 3.0 features (i.e. lambda ...

    Mastering.SoapUI.178398080X

    The final part of the book will show you how to virtualize a service response in SoapUI using Service Mocking. You will finish the journey by discovering the best practices for SoapUI test ...

    ember-graphql-mocking:使用Mock Service Worker(MSW)模拟GraphQL请求的Ember插件

    Ember插件,用于使用Mock Service Worker(MSW)模拟GraphQL请求。 :warning_selector: Bagaar使用的实验性模拟包。 您可能不应该使用此功能。 兼容性 Ember.js v3.16或更高版本 Ember CLI v2.13或更高版本 Node....

Global site tag (gtag.js) - Google Analytics