I have a class that after it does some stuff, sends a JMS message.
I'd like to unit test the "stuff", but not necessarily the sending of the message.
When I run my test, the "stuff" green bars, but then fails when sending the message (it should, the app server is not running).
What is the best way to do this, is it to mock the message queue, if so, how is that done.
I am using Spring, and "jmsTemplate" is injected, along with "queue".
The simplest answer I would use is to stub out the message sending functionality. For example, if you have this:
public class SomeClass {
public void doit() {
//do some stuff
sendMessage( /*some parameters*/);
}
public void sendMessage( /*some parameters*/ ) {
//jms stuff
}
}
Then I would write a test that obscures the sendMessage behavior. For example:
#Test
public void testRealWorkWithoutSendingMessage() {
SomeClass thing = new SomeClass() {
#Override
public void sendMessage( /*some parameters*/ ) { /*do nothing*/ }
}
thing.doit();
assertThat( "Good stuff happened", x, is( y ) );
}
If the amount of code that is stubbed out or obscured is substantial, I would not use an anonymous inner class but just a "normal" inner class.
You can inject a mocked jmsTemplate.
Assuming easymock, something like
JmsTemplate mockTemplate = createMock(JmsTemplate.class)
That would do the trick.
Regarding how to organise all those test stubbing / mocks in a larger application...
We build and maintain a larger Enterprise App, which is configured with Spring. The real App runs as EAR on a JBoss Appserver. We defined our Spring context(s) with a beanRefFactory.xml
<bean id="TheMegaContext"
class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<list>
<value>BasicServices.xml</value>
<value>DataAccessBeans.xml</value>
<value>LoginBeans.xml</value>
<value>BussinessServices.xml</value>
....
</list>
</constructor-arg>
</bean>
For running the unit tests, we just use a different beanRefFactory.xml, which exchanges the BasicServices to use a test version. Within that test version, we can define beans with the same names as in the production version, but with a mock/stub or whatever implementation (e.g. database uses a local Apache DPCP pooled datasource, while the production version uses the data source from the Appserver).
Another option is MockRunner which provides mock environments for JDBC, JMS, JSP, JCA and EJB. This allows you to define the queues/topics just like you would in the "real" case and simply send the message.
This is the perfect candidate to use jMock unit testing since your server is not running but you would use jMock to simulate interaction with the server.
Related
I have a moderately heavy springboot service, it takes 10-15 seconds to boot on a happy flow, and (1-2) minutes to fail on a retry/failover flow. This is ok for my business flows, and is how I expect a healthy service to behave.
I have integration tests (that run some end-to-end flows in my service), that can only test the actual integration status while the test machine (or dev machine) is connected to a specific VPN.
I want to auto skip integration tests if I'm not connected to VPN.
consider the following code
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {Server.class}, // auto scans a bunch of components
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // slow loading context
public class IntegrationTest {
#BeforeClass
public static void beforeClass() {
Assume.assumeTrue(DnsTool.vpnConnected()); // fast failing test
}
#Test
public void testIntegration() {
// some test logic
}
}
When the assumptions pass, my tests run, and all is good.
When the assumptions fail, my tests get skipped, but only after trying to load my expensive context.
How can I avoid the long running time for my test suite?
Things I tried:
Subclassing SpringJUnit4ClassRunner, and overriding isTestMethodIgnored.
Adding a TestExecutionListener, and throwing the assumption exception in beforeTestClass
These made no impression on Spring, and the context got loaded any way.
Things I didn't try:
Lazy init comes with 2.2.X next stable release of spring I think.
Lazy init potentially makes my problem go away, but I feel like there should be some easy spring-test/junit fix that I'm missing.
Thanks in advance for the help.
To me, this sounds like something that you shouldn't do in tests at all.
Tests (at least IMHO), are supposed to check the business cases and assume that the environment is set up and ready.
Maybe it worth to delegate this functionality to build tool and CI.
Example:
Define a profile in maven (or whatever build tool you use) that will run integration tests that require VPN. Define profile that will run all the rest of integration tests as well.
Activate the profile if some system property is available.
In CI tool (like Jenkins) as a part of CI even before you run maven, run the script that will check the VPN connection. Based on the results set the system properties and run maven with these properties. The required profiles will be loaded and all the tests / only tests that do not require VPN will be run.
Update
If you need to make it work from Spring (and it looks like you prefer this way),
Spring has a special annotation called #IfProfileValue
By default, it matches against system properties and if the value doesn't match the test gets ignored.
It looks something like this (and note that you can put this annotation on class as well, then it will work for all test methods in the class):
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTestClass {
#IfProfileValue(name = "os.name", values = {"Linux"})
#Test
public void testMe() {
// will run only in linux, otherwise
// won't even try to load an
// application context
....
}
}
This covers the case when you resolve the VPN connectivity externally and run the tests with a property. However, if you want to implement the VPN connectivity check in java, this annotation along not enough because it can work only with Java system properties, so in order to work with custom logic you need to implement org.springframework.test.annotation.ProfileValueSource:
public class VPNConnectivityProfileValueSource implements ProfileValueSource {
private String vpnEnabled = "true";
public VPNConnectivityProfileValueSource () {
// no spring context is available here
ClassPathResource resource = new ClassPathResource("vpn-config.properties");
if (resource.exists()) {
// read the VPN address,
//
//this.testProps = PropertiesLoaderUtils.loadProperties(resource);
// invoke your utility, check the connectivity, etc.
this.vpnEnabled = ...
}
}
#Override
public String get(String key) {
// this is important method,
if(key.equals("vpn.enabled") {
return this.vpnEnabled;
}
else return System.getProperty(key);
}
}
The last thing is to make the test aware of the ProfileValueSource:
For this there is another special annotation that you put on the test:
#ProfileValueSourceConfiguration(VPNConnectivityProfileValueSource.class)
All in all it the test can look like this:
#RunWith(SpringRunner.class)
#SpringBootTest
#ProfileValueSourceConfiguration(VPNConnectivityProfileValueSource.class)
#IfProfileValue(name = "vpn.enabled", value = "true")
public class MyTestClass {
#Test
public void testMe() {
....
}
}
All the classes/annotations I've mentioned reside in package org.springframework.test.annotation
I would like to test a class that provides a rest endpoint via JAX-RS. This class depends on a JPA EntityManager an thus on a database which needs to be populated prior to test execution. I saw solutions for database population like dbunit, but I want to populate the data directly from my test class (or delegated via object mother pattern). But when testing rest endpoints I need to use the annotation option #Deployment(testable = false) which refuses me to inject the EntityManager into my test class.
So how can I solve this situation?
Or are there any better best practices? (maybe mocking, but that's also not possible for black box tests)
You could create a bean to generate your test data:
#Startup
#Singleton
public class TestDataGenerator {
#PersistenceContext
private EntityManager em;
#PostConstruct
private void generateTestData() {
// Generate your test data
}
}
The TestDataGenerator class defined above is annotated with #Singleton (ensuring there will be only one instance of the class) and #Startup (for eager initialization during the application startup sequence).
Add the TestDataGenerator class to your Arquillian deployment:
#RunWith(Arquillian.class)
public class MyArquillianTest {
private Client client = ClientBuilder.newClient();
#Deployment
#RunAsClient
public static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive.class)
.addClasses(TestDataGenerator.class, ...)
.addAsResource("test-persistence.xml", "META-INF/persistence.xml")
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
}
#Test
public void testAPI(#ArquillianResource URL deploymentUrl) {
// Test your REST service
WebTarget target = client.target(deploymentUrl.toURI()).path("api");
}
}
Observe that #RunAsClient is equivalent to #Deployment(testable = false).
The #ArquillianResource annotation allows you to inject the URL to test your web application.
For tests, I usually try and separate black box and unit testing completely (I suppose it's a preference on how you do it).
For example, my REST Api could rely on whatever it wants, but usually it doesn't do much but call my database layer or some sort of facade accessing my DB layer. The objects are injected, yes, but usually I make the fields package private, which meant that you can set them from the same package (which works with Junit as well).
For example:
public class Facade1 {
#Inject
Facade facade;
public void doSomething() { ... }
}
This class represents my REST API. I could test doSomething by simply adding a mock object as facade. Mind you, this is a quite useless test, but you get the idea. Unit tests should happen in isolation with as much mocking as possible.
Now testing the actual Rest API I usually resort to a python integration tester. Python has nice http libraries that allow you to make request very easily.
For that, I set up a staging environment for my Rest Server. The environment is a live-like representation for testing. Everything there needs to works and needs to be on the same version as the production deployment.
I then use python to poke my REST Api and verify the responses. Since I've set up my staging environment I have complete control over database content. Therefore it is easy for me to test that all responses are correct.
My typical process then is:
Compile
Build
Deploy
Integration test
I hope that helps. If you want clearer examples, you might want to post a bit more code as it's a bit hard to imagine for me what it is exactly you'd like to do :)
Say I have the following route:
from(rabbitMQUri)
.to(myCustomerProcessor)
.choice()
.when(shouldGotoA)
.to(fizz)
.when(shouldGotoB)
.to(buzz)
.otherwise()
.to(foo);
Let's pretend that myCustomProcessor tunes shouldGotoA and shouldGotoB according to the message consumed from RabbitMQ.
I would like to unit test 3 scenarios:
A "fizz" message is consumed and shouldGotoA is set to true, which executes the first when(...).
A "buzz" message is consumed and shouldGotoB is set to true, which executes the second when(...).
A "foo" message is consumed and the otherwise() is executed.
My question is: how do I mock/stub the RabbitMQ endpoint so that the route executes as it normally will in production, but so that I don't have to actually connect the test to a RabbitMQ server? I need some kind of "mock message" producer.
A code example or snippet would be extremely helpful and very much so appreciated!
This is one way of putting together a suitable test.
Firstly define an empty Camel Context with just a ProducerTemplate in it:
<camel:camelContext id="camelContext">
<camel:template id="producerTemplate" />
</camel:camelContext>
I do this so that when I execute the test, I can control which routes actually start as I don't want all my routes starting during a test.
Now in the test class itself, you'll need references to the producer template and Camel Context. In my case, I'm using Spring and I autowire them in:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:/spring/spring-test-camel.xml" })
public class MyTest {
#Autowired
private ProducerTemplate producerTemplate;
#Autowired
private CamelContext camelContext;
In the test itself, replace the RabbitMQ/ActiveMQ/JMS component in the context with the seda or direct component. eg replace all JMS calls with a seda queue.
camelContext.removeComponent("jms");
camelContext.addComponent("jms", this.camelContext.getComponent("seda"));
camelContext.addRoutes(this.documentBatchRouting);
Now whenever you are reading or writing to a JMS URI, it is actually going to a seda queue. This is similar to injecting a new URI into the component but take less configuration and allows you to add new endpoints to the route without worrying about remembering to inject all the URIs.
Finally in the test, use the the producer template to send a test message:
producerTemplate.sendBody("jms:MyQueue", 2);
You're route should then execute and you can test your expectations.
Two things to note are:
Your transaction boundaries may change, especially if you replace JMS queues with a direct component
If you are testing several routes, you'll have to be careful to remove the route from the Camel Context at the end of the tests for that route.
It may depend what component you are using (AMQP or RabbitMQ) for the communication.
The single most important resource for sample code in Camel is the junit test cases in the source.
Two files that does similar things to what you need are these two, but you may want to look around in the test cases in general to get inspiration:
AMQPRouteTest.java
RabbitMQConsumerIntTest.java
A more "basic" way to make routes testable is to make the "from" uri a parameter.
Let's say you make your RouteBuilder something like this:
private String fromURI = "amqp:/..";
public void setFromURI(String fromURI){
this.fromURI = fromURI;
}
public void configure(){
from(fromURI).whatever();
}
Then you can inject a "seda:foobar" endpoint in the fromURI before your start the unit test. The seda endpoint is trivial to test. This assumes you don't need to test AMQP/RabbitMQ specific constructs, but simply receive the payload.
A good way to make software better testable (especially software that communicates to external stuff) is to use dependency injection. I love Guice and it is directly supported by camel.
(all this stuff will burden you with learning about dependency injection but it will pay very soon i can assure you)
In this scenario you would just inject "Endpoint"s. You pre-configure the endpoints like this (would be placed in "module").
#Provides
#Named("FileEndpoint")
private Endpoint fromFileEndpoint() {
FileEndpoint fileEndpoint = getContext().getEndpoint("file:" + somFolder, FileEndpoint.class);
fileEndpoint.setMove(".done");
fileEndpoint.setRecursive(true);
fileEndpoint.setDoneFileName(FtpRoutes.DONE_FILE_NAME);
...
return fileEndpoint;
}
Your RouteBuilder just inject the endpoint:
#Inject
private MyRoutes(#Named("FileEndpoint") final Endpoint fileEndpoint) {
this.fileEndpoint = fileEndpoint;
}
#Override
public void configure() throws Exception {
from(fileEndpoint)....
}
To easily test such an route you inject another endpoint for test not FileEndpoint but "direct:something". A very easy way to do this is "Jukito", it combines Guice with Mockito. A test would look like this:
#RunWith(JukitoRunner.class)
public class OcsFtpTest extends CamelTestSupport {
public static class TestModule extends JukitoModule {
#Override
protected void configureTest() {
bind(CamelContext.class).to(DefaultCamelContext.class).in(TestSingleton.class);
}
#Provides
#Named("FileEndpoint")
private Endpoint testEndpoint() {
DirectEndpoint fileEndpoint = getContext().getEndpoint("direct:a", DirectEndpoint.class);
return fileEndpoint;
}
}
#Inject
private MyRoutes testObject;
#Test
....
}
Now the "testObject" will get the direct endpoint instead of the file endpoint.This works with all kinds of Endpoints and generally with all Interfaces/ abstract classes and Apis that heavily rely on Interfaces (camel excels here!).
I want to unit test single routes configured in java that uses beans. I read in camel in action (chapter 6.1.4) how to do this:
protected RouteBuilder createRouteBuilder() throws Exception {
return new myRoute();
}
But in my case the rout needs some beans to be registered. I know how to register beans in standalone app: see here
But how to register beans within "CamelTestSupport"? Is there a way to use beans without registry? Probably by injecting them (all beans hav no arg constructors)? I am using Guice and in my tests i am using Jukito (Guice+Mockito).
Afer Camel 3.0.0
You can now update the JNDI registry from anywhere you have access to the camel context.
context.getRegistry().bind("myId", myBean);
More info available here https://camel.apache.org/manual/latest/camel-3-migration-guide.html#_camel_test
Before Camel 3.0.0
You need to override the createRegistry() method,
#Override
protected JndiRegistry createRegistry() throws Exception {
JndiRegistry jndi = super.createRegistry();
//use jndi.bind to bind your beans
return jndi;
}
#Test
public void test() {
//perform test
}
No, you cannot use beans without registry.
You need to use the registry to hold the beans instance, otherwise Camel cannot look up the bean for you. You just need to override the createRegistry() method to setup right registry with your beans if your test class extends CamelTestSupport.
The answer provided by #Matthew Wilson is no longer recommended starting with Camel 3.0.0
His solution is still in the ballpark but the implementation details have changed. I have chosen to inject it in setUp (the example is in Kotlin, use your IDE hints to produce the same in Java):
override fun setUp() {
super.setUp()
context.registry.bind("yourBean", YourBean())
}
As you can see, the registry is still involved but now you can only get it from the context. I consider it cleaner to keep these kinds of setup routines in the conveniently named overrideable method setUp. Just don't forget to call the parent version.
If there is a better place to put this kind of routines in, let me know so I can upgrade the answer.
Docs: https://camel.apache.org/manual/latest/camel-3-migration-guide.html
Note: I have purposefully removed words from the names of classes and objects, so please excuse the horribly names in my code examples
I've got a test class that sets up my application using some test Spring context files. The application is a wrapper to a web service.
As this is a test, I have mocked the main interface to the web service in question (the ITransporter class). This gives me the ability to set expectations so that I can check that the requests sent to the web service are: in the expected format; have the expected fields complete; etc...
My mock is defined in a file called test-beans-context.xml and is passed into some service beans, as follows:
<bean id="mockTransporter" class="org.easymock.EasyMock" factory-method="createMock" scope="singleton">
<constructor-arg index="0" value="transport.ITransporter" />
</bean>
<bean id="accountService" class="service.AccountService">
<property name="transporter" ref="mockTransporter" />
</bean>
This context file is used in 2 places. (And I fear this is where my problem arises.)
The first being the test class, which is defined as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration( locations={"classpath:test-beans-context.xml"} )
public class AbstractIntegrationTest {
#Autowired
private ITransporter mockTransporter;
//Some tests that perform expectations like the following:
// EasyMock.reset( this.mockTransporter );
// EasyMock.expect( this.mockTransporter.sendRequest( EasyMock.capture(this.requestXmlCapture) ) ).andReturn( responseXml );
}
The second place is in a class that is within the logical trail for getting to sending the request. It loads a separate XML context file /lite-api-context.xml that then imports the test one in my test set up.
public class Factory implements IFactory {
public Factory() {
context = new ClassPathXmlApplicationContext("/lite-api-context.xml");
}
#Override
public IAccountService getAccountService() {
return (IAccountService) context.getBean("accountService");
}
}
And lite-api-context.xml includes this:
<import resource="classpath:/test-beans-context.xml" />
My problem is that in the test class, I'm getting a different instance of the mocked ITransporter to the one that is ultimately being used by my other services. So the expectations I set up are never actually executed as the mocks end up being different instances.
Is there a way to ensure I get the same instance in both places?
Or am I going to have to create my own singleton test implementation of the ITransporter interface? (Basically creating a stub that behaves exactly like my mock does now.)
EDIT: (Answer)
As The Thom said, it appears I need to create my own class to manage the mock.
I wanted to add my solution here too in case anyone stumbled across a similar problem.
Just wrote a quick static class like this:
public class MockTransporter {
private static ITransporter mockTransporter = EasyMock.createMock(ITransporter.class);
public static final ITransporter getInstance() {
return mockTransporter;
}
}
And had to change the XML config to this:
<bean id="mockTransporter" class="MockTransporter" factory-method="getInstance" />
Oh yeah, that's a problem. When you create a new context that's like creating a new object space for Spring. The one created in your XML file is different from the one created in your handmade context. They will always produce different variables.
I've been burned on that one before.
You're only hope if you want the same ones is to manage your own singletons.
The ideal way to solve this problem would be to create the Factory as a Spring bean as well, and inject the AccountService into the Factory bean.
In general context.getBean() should be avoided in production code because it harms the concept of Inversion of Control (for more information see: Why is Spring's ApplicationContext.getBean considered bad?). It's okay to use it in test code though.