`

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 ...

    Better Unit Testing with Microsoft Fakes (RTM)

    If you are new to terms like unit testing and mocking, we still think that this guide will be useful in introducing you to these important concepts that developers need in their tool belt.

    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 ...

    Developer.Testing.Building.Quality.into.Software

    You’ll learn how to design for testability and utilize techniques like refactoring, dependency breaking, unit testing, data-driven testing, and test-driven development to achieve the highest ...

    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, ...

    Packt Publishing Mockito Essentials (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 ...

    Agile.Android.1484297008

    What is mock testing and how to use Mockito in your Android apps What are and how to use third party tools like Hamcrest, Roblectric, Jenkins and more How to apply test driven development (TDD) to ...

    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 ...

    The.Way.of.the.Web.Tester.A.Beginners.Guide.to.Automating.Tests

    This book will help you bridge that testing gap between your developers and your testers by giving your team a model to discuss automated testing, and most importantly, to coordinate their efforts. ...

    [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 ...

    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....

    Agile Swift(Apress,2016)

    This short step by step guide walks you through unit testing, mocking and continuous integration and how to get these key ingredients running in your Swift projects. This book also looks at how to ...

    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 ...

    PowerMock资料大全(E文)

    Developers familiar with the supported mock frameworks will find PowerMock easy to use, since the entire expectation API is the same, both for static methods and constructors. PowerMock aims to ...

    Test-Driven Development with Python 【第二版】

    from scratch—to teach TDD methodology and how it applies to web programming, from the basics of database integration and Javascript to more advanced topics such as mocking, Ajax, and REST APIs....

    .Net单元测试Mocking框架 Moq 官方帮助文档 chm

    它的目标是让Mocking以一种自然的方式与现有单元测试进行集成,使它更加简单、直观,以避免开发人员被迫重写测试或学习需要大量录制/播放的Mock框架。Moq的目标就是为了帮助那些编写测试代码,但却不使用Mocking框架...

    Laravel开发-laravel-dusk-mocking

    Laravel开发-laravel-dusk-mocking 拉拉维尔黄昏试验模拟立面。

    Test-.Driven.Python.Development.1783987928

    Additionally, we will use mocking to implement the parts of our example project that depend on other systems. Towards the end of the book, we'll take a look at the most common patterns and anti-...

    Test-Driven Java Development(PACKT,2015)

    Test-driven development (TDD) is a development ...Develop an application to implement behaviour-driven development in conjunction with unit testing Enable and disable features using Feature Toggles

    Introduction.to.Android.Application.Development(4th,2013.12) pdf

    Displaying Text to Users with TextView 179 Configuring Layout and Sizing 179 Creating Contextual Links in Text 180 Retrieving Data from Users with Text Fields 183 Retrieving Text Input Using EditText ...

Global site tag (gtag.js) - Google Analytics