1. Overview
1.概述
In this tutorial, we’ll create a simple Spring application that connects to ActiveMQ to send and receive messages. We’ll focus on testing this application and the different approaches to test Spring JMS overall.
在本教程中,我们将创建一个简单的Spring应用程序,连接到ActiveMQ来发送和接收消息。我们将专注于测试这个应用程序以及测试Spring JMS整体的不同方法。
2. Application Setup
2.应用程序的设置
First, let’s create a basic application that can be used for testing. We’ll need to add the necessary dependencies and implement the message handling.
首先,让我们创建一个可用于测试的基本应用程序。我们需要添加必要的依赖性并实现消息处理。
2.1. Dependencies
2.1.依赖性
Let’s add the required dependencies to our project’s pom.xml. We need Spring JMS to be able to listen to JMS messages. We’ll use ActiveMQ-Junit to start an embedded ActiveMQ instance for a part of the tests and TestContainers to run an ActiveMQ Docker container in the other tests:
让我们在我们项目的pom.xml中添加所需的依赖项。我们需要Spring JMS以便能够监听JMS消息。我们将使用ActiveMQ-Junit为部分测试启动一个嵌入式ActiveMQ实例,并使用TestContainers在其他测试中运行一个ActiveMQ Docker容器:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.3.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.activemq.tooling</groupId>
<artifactId>activemq-junit</artifactId>
<version>5.16.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.17.3</version>
<scope>test</scope>
</dependency>
2.2. Application Code
2.2.应用代码
Now let’s create a Spring application that can listen for messages:
现在让我们创建一个可以监听消息的Spring应用程序。
@ComponentScan
public class JmsApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(JmsApplication.class);
}
}
We need to create a configuration class and enable JMS with the @EnableJms annotation and configure the ConnectionFactory to connect to our ActiveMQ instance:
我们需要创建一个配置类,用@EnableJms注解启用JMS,并配置ConnectionFactory以连接到我们的ActiveMQ实例。
@Configuration
@EnableJms
public class JmsConfig {
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
return factory;
}
@Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory("tcp://localhost:61616");
}
@Bean
public JmsTemplate jmsTemplate() {
return new JmsTemplate(connectionFactory());
}
}
After this, let’s create our listener that can receive and process messages:
在这之后,让我们创建可以接收和处理消息的监听器。
@Component
public class MessageListener {
private static final Logger logger = LoggerFactory.getLogger(MessageListener.class);
@JmsListener(destination = "queue-1")
public void sampleJmsListenerMethod(TextMessage message) throws JMSException {
logger.info("JMS listener received text message: {}", message.getText());
}
}
We’ll also need a class that can send messages:
我们还需要一个可以发送消息的类。
@Component
public class MessageSender {
@Autowired
private JmsTemplate jmsTemplate;
private static final Logger logger = LoggerFactory.getLogger(MessageSender.class);
public void sendTextMessage(String destination, String message) {
logger.info("Sending message to {} destination with text {}", destination, message);
jmsTemplate.send(destination, s -> s.createTextMessage(message));
}
}
3. Testing With Embedded ActiveMQ
3.使用嵌入式ActiveMQ进行测试
Let’s test our application. We’ll use an embedded ActiveMQ instance first. Let’s create our test class and add a JUnit Rule that manages our ActiveMQ instance:
让我们来测试我们的应用程序。我们将首先使用一个嵌入式的ActiveMQ实例。让我们创建我们的测试类并添加一个JUnit规则来管理我们的ActiveMQ实例。
@RunWith(SpringRunner.class)
public class EmbeddedActiveMqTests4 {
@ClassRule
public static EmbeddedActiveMQBroker embeddedBroker = new EmbeddedActiveMQBroker();
@Test
public void test() {
}
// ...
}
Let’s run this empty test and inspect the logs. We can see that an embedded broker is started with our test:
让我们运行这个空测试并检查日志。我们可以看到,一个嵌入式经纪商与我们的测试一起启动了。
INFO | Starting embedded ActiveMQ broker: embedded-broker
INFO | Using Persistence Adapter: MemoryPersistenceAdapter
INFO | Apache ActiveMQ 5.14.1 (embedded-broker, ID:DESKTOP-52539-254421135-0:1) is starting
INFO | Apache ActiveMQ 5.14.1 (embedded-broker, ID:DESKTOP-52539-254421135-0:1) started
INFO | For help or more information please see: http://activemq.apache.org
INFO | Connector vm://embedded-broker started
INFO | Successfully connected to vm://embedded-broker?create=false
The broker is stopped after all the tests are executed in our test class.
在我们的测试类中执行完所有的测试后,经纪人就会被停止。
We need to configure our application to connect to this ActiveMQ instance so that we can test our MessageListener and MessageSender classes properly:
我们需要配置我们的应用程序以连接到这个ActiveMQ实例,以便我们能够正确测试我们的MessageListener和MessageSender类。
@Configuration
@EnableJms
static class TestConfiguration {
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
return factory;
}
@Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory(embeddedBroker.getVmURL());
}
@Bean
public JmsTemplate jmsTemplate() {
return new JmsTemplate(connectionFactory());
}
}
This class uses a special ConnectionFactory which gets the URL from our embedded broker. Now we need to use this configuration by adding the @ContextConfiguration annotation to the class containing our tests:
这个类使用了一个特殊的ConnectionFactory,它从我们的嵌入式代理那里获得了URL。现在我们需要通过向包含我们测试的类添加@ContextConfiguration注解来使用这个配置。
@ContextConfiguration(classes = { TestConfiguration.class, MessageSender.class }) public class EmbeddedActiveMqTests {
3.1. Sending Messages
3.1.发送信息
Let’s write our first test and check the functionality of our MessageSender class. First, we need to get a reference to an instance of this class by simply injecting it as a field:
让我们来写第一个测试,检查我们的MessageSender类的功能。首先,我们需要通过简单地注入该类的一个字段来获得对该类实例的引用。
@Autowired
private MessageSender messageSender;
Let’s send a simple message to ActiveMQ and add some assertions to check the functionality:
让我们向ActiveMQ发送一个简单的消息,并添加一些assertions以检查其功能。
@Test
public void whenSendingMessage_thenCorrectQueueAndMessageText() throws JMSException {
String queueName = "queue-2";
String messageText = "Test message";
messageSender.sendTextMessage(queueName, messageText);
assertEquals(1, embeddedBroker.getMessageCount(queueName));
TextMessage sentMessage = embeddedBroker.peekTextMessage(queueName);
assertEquals(messageText, sentMessage.getText());
}
Now we are sure that our MessageSender works properly because the queue contains exactly one entry with the correct text after we send the message.
现在我们确信我们的MessageSender工作正常,因为在我们发送消息后,队列中正好有一个带有正确文本的条目。。
3.2. Receiving Messages
3.2.接收信息
Let’s check our listener class as well. Let’s start with creating a new test method and sending a message with the embedded broker. Our listener is set to use “queue-1” as its destination, so we need to ensure that we are using the same name here.
让我们也检查一下我们的监听器类。让我们从创建一个新的测试方法开始,用嵌入式代理发送一条消息。我们的监听器被设置为使用 “queue-1 “作为其目的地,所以我们需要确保我们在这里使用相同的名称。
Let’s use Mockito to check the listener’s behavior. We’ll use the @SpyBean annotation to get an instance of the MessageListener:
让我们使用Mockito来检查监听器的行为。我们将使用@SpyBean注解来获取MessageListener的实例:。
@SpyBean
private MessageListener messageListener;
Then, we’ll check if the method was called and capture the received method argument with an ArgumentCaptor:
然后,我们将检查该方法是否被调用,并用ArgumentCaptor捕获收到的方法参数。
@Test
public void whenListening_thenReceivingCorrectMessage() throws JMSException {
String queueName = "queue-1";
String messageText = "Test message";
embeddedBroker.pushMessage(queueName, messageText);
assertEquals(1, embeddedBroker.getMessageCount(queueName));
ArgumentCaptor<TextMessage> messageCaptor = ArgumentCaptor.forClass(TextMessage.class);
Mockito.verify(messageListener, Mockito.timeout(100)).sampleJmsListenerMethod(messageCaptor.capture());
TextMessage receivedMessage = messageCaptor.getValue();
assertEquals(messageText, receivedMessage.getText());
}
We can now run the tests, and both of them pass.
我们现在可以运行测试了,它们都通过了。
4. Testing With TestContainers
4.使用TestContainers进行测试
Let’s see another approach for testing JMS in Spring applications. We can use TestContainers to run an ActiveMQ Docker container and connect to it in our tests.
让我们看看另一种在Spring应用程序中测试JMS的方法。我们可以使用TestContainers来运行一个ActiveMQ Docker容器,并在我们的测试中连接到它。
Let’s create a new test class and include the Docker container as a JUnit Rule:
让我们创建一个新的测试类,并将Docker容器作为一个JUnit规则包括在内。
@RunWith(SpringRunner.class)
public class TestContainersActiveMqTests {
@ClassRule
public static GenericContainer<?> activeMqContainer
= new GenericContainer<>(DockerImageName.parse("rmohr/activemq:5.14.3")).withExposedPorts(61616);
@Test
public void test() throws JMSException {
}
}
Let’s run this test and check the logs. We can see some information related to TestContainers as it is pulling the specified docker image and as it starts the container:
让我们运行这个测试并检查日志。我们可以看到与TestContainers有关的一些信息,因为它正在拉取指定的docker镜像,并且在启动容器时:。
INFO | Creating container for image: rmohr/activemq:5.14.3
INFO | Container rmohr/activemq:5.14.3 is starting: e9b0ddcd45c54fc9994aff99d734d84b5fae14b55fdc70887c4a2c2309b229a7
INFO | Container rmohr/activemq:5.14.3 started in PT2.635S
Let’s create a configuration class similar to what we implemented with ActiveMQ. The only difference is the configuration of the ConnectionFactory:
让我们创建一个与我们在ActiveMQ中实现的类似的配置类。唯一的区别是对ConnectionFactory的配置。
@Bean
public ConnectionFactory connectionFactory() {
String brokerUrlFormat = "tcp://%s:%d";
String brokerUrl = String.format(brokerUrlFormat, activeMqContainer.getHost(), activeMqContainer.getFirstMappedPort());
return new ActiveMQConnectionFactory(brokerUrl);
}
4.1. Sending Messages
4.1.发送信息
Let’s test our MessageSender class and see if it works with this Docker container. This time we can’t use the methods on the EmbeddedBroker, but the Spring JmsTemplate is easy to use too:
让我们测试一下我们的MessageSender类,看看它是否能在这个Docker容器中工作。这次我们不能使用EmbeddedBroker上的方法,但Spring的JmsTemplate也很容易使用:。
@Autowired
private MessageSender messageSender;
@Autowired
private JmsTemplate jmsTemplate;
@Test
public void whenSendingMessage_thenCorrectQueueAndMessageText() throws JMSException {
String queueName = "queue-2";
String messageText = "Test message";
messageSender.sendTextMessage(queueName, messageText);
Message sentMessage = jmsTemplate.receive(queueName);
Assertions.assertThat(sentMessage).isInstanceOf(TextMessage.class);
assertEquals(messageText, ((TextMessage) sentMessage).getText());
}
We can use the JmsTemplate to read the contents of the queue and check if our class sent the correct message.
我们可以使用JmsTemplate来读取队列的内容并检查我们的类是否发送了正确的消息。
4.2. Receiving Messages
4.2.接收信息
Testing our listener class isn’t much different either. Let’s send a message using the JmsTemplate and verify if our listener received the correct text:
测试我们的监听器类也没有什么不同。让我们使用JmsTemplate发送一条消息,并验证我们的监听器是否收到了正确的文本:。
@SpyBean
private MessageListener messageListener;
@Test
public void whenListening_thenReceivingCorrectMessage() throws JMSException {
String queueName = "queue-1";
String messageText = "Test message";
jmsTemplate.send(queueName, s -> s.createTextMessage(messageText));
ArgumentCaptor<TextMessage> messageCaptor = ArgumentCaptor.forClass(TextMessage.class);
Mockito.verify(messageListener, Mockito.timeout(100)).sampleJmsListenerMethod(messageCaptor.capture());
TextMessage receivedMessage = messageCaptor.getValue();
assertEquals(messageText, receivedMessage.getText());
}
5. Conclusion
5.结论
In this article, we created a basic application that can send and receive messages with Spring JMS. Then, we discussed two ways to test it.
在这篇文章中,我们创建了一个基本的应用程序,可以用Spring JMS发送和接收消息。然后,我们讨论了测试它的两种方法。
Firstly, we used an embedded ActiveMQ instance that even provides some convenient methods to interact with the broker. Secondly, we used TestContainers to test our code with a docker container that simulates real-world scenarios better.
首先,我们使用了一个嵌入式的ActiveMQ实例,它甚至提供了一些方便的方法来与代理进行交互。其次,我们使用TestContainers来测试我们的代码,用docker容器更好地模拟真实世界的场景。
As always, the source code for these examples is available over on GitHub.
一如既往,这些示例的源代码可在GitHub上获取。