fredag 2 maj 2014

Mule mockless Integration testing tip, or "in wait for a mature Munit"

I really like the Munit framework that is beeing developed.
https://github.com/mulesoft/munit/wiki
and I will continue using it as soon as it gets more mature and stable. Especially when the Anypoint studio (former Mule studio) support is more stable.

However the experience so far as an early adopter has given me issues I can't spend time on during day to day production work.

So how can I do Mule integration tests without Munit and without having to mock every endpoint that has environmental bindings?

One way of doing it which I would like to share is by using the
org.mule.tck.junit4.FunctionalTestCase
as described in the Mulesoft documentation but with a little twist.

To be able to interact with a "full featured" environment without mocking or modification of the application  I make use of the fact that Mule is based on Spring. I use a combination of the Mulesoft FunctionalTestCase explained above and Springs JUnit framwork along with mavens failsafe plugin.

For example let's say I would like to have a integation test that tests the results from a flow with a http endpoint that has a <mule-ss:http-security-filter> based where basic authentication is verified against a configured <mule-ss:security-manager> without using any mocking or .

 <flow name="ToTest" doc:name="ToTest">  
     <inbound-endpoint name="testendpoint" address="${endpointbase_address_in}" doc:name="Generic" exchange-pattern="request-response" mimeType="application/json">  
          <!-- first authenticate -->  
                <mule-ss:http-security-filter realm="mule-realm"/>  
                <!-- check that user is autorized to execute this flow -->   
          <custom-security-filter class="se.redpill.pnd.mulecomponents.SpringSecurityRoleFilter">  
            <spring:property name="allowedAuthorities" value="ROLE_SUPPLIER" />  
          </custom-security-filter>  
     </inbound-endpoint>  
           <jersey:resources doc:name="REST">  
                <custom-interceptor class="se.redpill.pnd.util.LoggingInterceptor"/>   
                <component>  
                     <spring-object bean="testDataBean" />  
                </component>  
           </jersey:resources>

           ....
           flow execution ....
           ....


This would require the test setup to send in a valid username and password to the http endpoint to be accepted and for the test to be able to evaluate the outcome of the flow.

To be able to do this we would need to share configuration between the application itself and the test setup even if the application has environment based configuration like maven filtering and / or property file overrides.

This could be property placeholders holding usernames for in-memory authentication providers or property placeholders or for database based authentication etc, but also for environment specific Spring application context setup like beans , spring security settings, annotation config etc.

Another thing I want to make sure is that unit test environment is not disturbed by my integration test setup.


 @Configuration  
 @PropertySource({"classpath:/myacceptancetest.properties", "classpath:/mytest.properties"})  
 @RunWith(SpringJUnit4ClassRunner.class)  
 @ContextConfiguration("/test-application-context.xml")  
 public class ITTestData extends FunctionalTestCase{  
      @Autowired   
      Environment env;  
      @Override  
      protected String getConfigResources() {  
           return "MyTestflow.xml";  
      }  
      @Test  
      public void testSend() throws Exception  
      {  
        MuleClient client = new MuleClient(muleContext);  
        String payload = FileUtils.readFileToString(new File("src/test/resources/testdata.json"));  
        Map<String, Object> properties = new HashMap<String, Object>();  
        properties.put("Content-Type", new String("application/json"));  
        ImmutableEndpoint endpoint = muleContext.getRegistry().lookupObject("testendpoint");  
        assertNotNull(endpoint);  
        String username = env.getProperty("test.user");  
        String passwd = env.getProperty("test.password");  
        String address = endpoint.getEndpointURI().getAddress();  
        int pos = address.indexOf("://");  
        String beginaddress = address.substring(0, pos+ 3);  
        String endaddress = address.substring(pos+3, address.length());  
        address = beginaddress + username + ":" + passwd + "@" + endaddress + "/signe/pnd/register/enkat";  
        MuleMessage result = client.send(address, payload, properties);  
        assertEquals("{\"Status\" : \"OK\"}", result.getPayloadAsString());  
      }  
 }  

Lets have a look at the example above.

We are extending the FunctionalTestCase and we specify that we want to launch a complete Mule instance with configuration found in the "MyTestflow.xml" by overriding getConfigResources().
The instance will live only during the test and will be launched and teared down accordingly. This will also make sure that all the referenced context property placeholder resources associated with that xml file would be activated for our test. However these are only available within the muleContext and values in the properties files are not automatically registered in the mule registry so we can't get hold of them easily during runtime.

To handle that we use  4 Spring configuration annotations. It tells the test which property sources to use (note that these are shared so specify both the ones your are testing i.e. the same as the MyTestflow references to (first) and the ones you are overriding or complementing the test with (after).
It also tells the test which Spring application context to use if you have any settings or beans etc that differs during the test from production set another application context herem otherwise use the same as the one referenced to from myTestflow.xml.

Thats it. We now have the same contextual setup in the test as the environment we are testing which allows us true integrational testing.

In the example above we are using a mule client to call the endpoint and we get the actual endpoint address to call by doing a runtime lookup in the Mule registry (i.e no matter what configuration is used we will get the address that the Mule instance is configured with).

We set the json payload from a file and send it to the endpoint with a username and password from our environment shared properties.
In a more complete case the user and password used would be created before and deleted after the test to only live during the lifetime of the actual integration test.

Another beautiful thing about this setup is that it would run just perfectly within Anypoint (Mule) studio when executing your normal unit tests.

So how about maven?

Yes. It would run just fine in a Maven integration-test phase as well together with the failsafe plugin. To separate it from your unit tests which I presume you run in your test phase.

Lets take a look at how it could be configured in your pom.xml

As stated in the Mule docs integration test classes are named IT* or *IT or *ITCase and are located under src/it/java , so we need to tell maven about this to make sure that the integration test classes are compiled and loaded correctly.


  <plugin>  
     <groupId>org.codehaus.mojo</groupId>  
     <artifactId>build-helper-maven-plugin</artifactId>  
     <executions>  
      <execution>  
       <id>add-test-source</id>  
       <phase>generate-test-sources</phase>  
       <goals>  
        <goal>add-test-source</goal>  
       </goals>  
       <configuration>  
        <sources>  
         <source>src/it/java</source>  
        </sources>  
       </configuration>  
      </execution>  
     </executions>  
    </plugin>  

Just add another plugin section to your pom.xml sepecifying "generate-test-sources" as phase and "src/it/java" as source path.

And finally another plugin specification for the actual failsafe configuration:


 <plugin>  
     <groupId>org.codehaus.mojo</groupId>  
     <artifactId>failsafe-maven-plugin</artifactId>  
     <executions>  
      <execution>  
       <id>integration-test</id>  
       <phase>integration-test</phase>  
       <goals>  
        <goal>integration-test</goal>  
       </goals>  
      </execution>  
      <execution>  
       <id>verify</id>  
       <goals>  
        <goal>verify</goal>  
       </goals>  
      </execution>  
     </executions>  
    </plugin>  

Set "integration-test" as phase and BAM....now you can do:

mvn test

to execute your normal unit tests and

mvn integration-test

to run the integration test phase with your now full blown integration test!

Happy testing until Munit comes to conquer!

1 kommentar:

  1. Great Article. its is very very helpful for all of us and I never get bored while reading your article because, they are becomes a more and more interesting from the starting lines until the end.

    Mulesoft online training india

    SvaraRadera