Posted by Ole Lensmar on Monday, January 30, 2012

This time around let's do something really low-level and useful: use the soapUI extensions mechanisms to create a custom TestStep for sending an email during the execution of a TestCase. Since we have quite a lot of ground (and code!) to cover, let's get started.

Short Overview

soapUI internally uses a number of registries containing factories for core objects, for example TestSteps, assertions, desktop panels etc. (see Custom Factories for an extensive list). To create a custom TestStep, we therefore need to supply at least one factory: that for creating the TestStep itself - a TestStepFactory. Additionally (but not required) since we also want to provide a nice panel inside soapUI for editing the actual email and related properties we need to provide another factory for creating the PanelBuilder that creates this editor panel - a PanelBuilderFactory.

soapUI makes heavy use of the apache XMLBeans framework for holding configurations for internal objects - these are generated from the soapui.xsd file included in the soapUI source code. You will see how the state of our TestStep is read and written to objects created for this purpose a bit further down.

Confusing? Don't worry - and to be honest, looking at this code which has been written over the last seven years, there are many things that could have been done differently (and better for sure) - bear with us!

1. Create and register the TestStep Factory and TestStep class

As mentioned, the TestStep itself is created by a TestStepFactory - a WsdlTestStepFactory to be exact (legacy naming...) - here it is:

public class EMailTestStepFactory extends WsdlTestStepFactory
{
private static final String EMAIL_STEP_ID = "email";
public EMailTestStepFactory()
{
super( EMAIL_STEP_ID, "EMail TestStep", "Sends an email", "email.png" );
}
public WsdlTestStep buildTestStep( WsdlTestCase testCase, TestStepConfig config,  boolean forLoadTest )
{
return new EMailTestStep( testCase, config, forLoadTest );
}
public TestStepConfig createNewTestStep( WsdlTestCase testCase, String name )
{
TestStepConfig testStepConfig = TestStepConfig.Factory.newInstance();
testStepConfig.setType( EMAIL_STEP_ID );
testStepConfig.setName( name );
return testStepConfig;
}
public boolean canCreate()
{
return true;
}
}

Let's have a quick look at the methods:

  • The constructor invokes the super constructor with a number of basic properties, including the unique ID for the TestStep and the path to an icon for representing the TestStep (thanks for FamFamFam!). The path can be either absolute or relative to the current working directory.
  • The createNewTestStep method is called by soapUI when the user selects to create a new TestStep of our type, it returns an XmlBeans TestStepConfig (from the corresponding TestStep complex type in the soapUI XSD) that will be the basis for the state of the TestStep and its configuration
  • The buildTestStep method is called to create the actual TestStep - it is passed the containing TestCase (TestSteps must always be in a TestCase) and the corresponding TestStepConfig object that was at some time created by the above createNewTestStep method.
  • The canCreate method simply tells soapUI that the factory is working ok - here you could add a check of some external conditions required for this factory to be active, for example a license-check.

Of course we also need to create the EMailTestStep class returned by the buildTestStep method:

public class EMailTestStep extends WsdlTestStepWithProperties
{
protected EMailTestStep( WsdlTestCase testCase, TestStepConfig config, boolean forLoadTest )
{
super( testCase, config, true, forLoadTest );

if( !forLoadTest )
{
setIcon( UISupport.createImageIcon( "email.png" ) );
}
}

@Override
public TestStepResult run( TestCaseRunner testRunner, TestCaseRunContext context )
{
WsdlTestStepResult result = new WsdlTestStepResult( this );
result.setStatus( TestStepStatus.OK );
return result;
}
}

 

This is almost the simplest possible implementation;

  • The constructor passes all arguments to its super-class and initializes the icon if we are not running under a LoadTest (either in soapUI or loadUI) – when the icon isn’t needed anyway.
  • The run method is what is called by soapUI during TestCase execution – our default implementation just creates a default result object, sets its status to OK and returns it.This is where we will do the actual sending of the email a bit further down.

These two classes are actually enough to get things started in soapUI, all that is missing is an xml configuration file that adds the factory to soapUI - it should be placed in the soapUI\bin\factories folder and named anything ending with -factories.xml (we will call it email-teststesp-factories.xml). It contains the following:

<?xml version="1.0" encoding="UTF-8"?>
<tns:soapui-factories xmlns:tns="http://eviware.com/soapui/config">
<tns:factory id="EMailTestStep"
factoryType="com.eviware.soapui.impl.wsdl.teststeps.registry.WsdlTestStepFactory"
factoryClass="soapui.demo.teststeps.email.EMailTestStepFactory"/>

</tns:soapui-factories>

As you can see this simply registers our factory of type WsdlTestStepFactory with soapUI.

Great! After compiling and packaging these classes into a jar file and dropping them into the soapUI\bin\ext folder together with the factories xml file in the factories folder we can start soapUI – and the magic begins!
On startup you should see the following rows in the soapUI log at the bottom of the soapUI window:

Wed Jan 25 00:03:51 CET 2012:INFO:Adding factories from [C:\..\factories\email-teststep-factories.xml]
Wed Jan 25 00:03:51 CET 2012:INFO:Adding factory [class soapui.demo.teststeps.email.EMailTestStepFactory]

Awesome! Now if we create an empty project and just add a TestSuite and TestCase we will see the following in the TestCase window:

teststeps-toolbar

And pressing that button (or using the right-click menu in the empty list of TestSteps ) allows us to add an Email TestStep:

Created-teststep

Finally, running the TestCase will show the following in the TestCase Run Log at the bottom of the TestCase window:

initial-testrun-log

Great! Of course we can save / reload the project, add more TestSteps, move them around, clone them, etc. - everything that the soapUI infrastructure allows now also supports our (totally useless) TestSep.

As a last improvement before we add the desktop panel, let's fix the right-click menu of the TestStep. Currently it looks like this:

Original-right-click-menu

This is missing some key actions, including the actions to open the TestSteps editor panel (which will create below), enable/disable the TestStep, get help, etc. < /p>

What we need to do is create a corresponding popup menu definition for our TestStep in a custom email-teststep-actions.xml file that we save to the soapUI\bin\actions folder (which plugs into the actions infrastructure in soapUI):

<?xml version="1.0" encoding="UTF-8"?>
<tns:soapui-actions xmlns:tns="http://eviware.com/soapui/config">
<tns:actionGroup name="EMail TestStep Actions"
id="EMailTestStepActions"
class="com.eviware.soapui.impl.wsdl.actions.teststep.WsdlTestStepSoapUIActionGroup">
<tns:actionMapping actionId="SeperatorAction" />
<tns:actionMapping actionId="ShowOnlineSoapUIHelp"
keyStroke="F1" param="http://blog.smartbear.com" />
</tns:actionGroup>
</tns:soapui-actions>

Here we define a soapUI "action-group" which is used to populate the popup menu above - the id of the group matches the actual name of the TestStep class and it uses a custom SoapUIActionGroup implementation internally (which adds these actions and an "Open Editor" action to the popup you can see above).  We only add a custom Separator and Online Help action - the custom WsdlTestStepSoapUIActionGroup does the rest. Save the file and restart soapUI - you will now see the following row in the soapUI log:

INFO:Adding actions from [C:\..\actions\email-teststep-actions.xml]

Now that we have this file registered the right-click menu looks a little nicer:

Time to move on!

2. Create a PanelBuilderFactory and its DesktopPanel

The next step is to create a similar skeleton for the desktop panel that we want to show to our users when they double-click the TestStep (which for now does nothing). Once again we will create a Factory, this time for a PanelBuilder that is used by soapUI to build both the main desktop panel and the properties panel in the bottom left for our new TestStep. Let's start with the Factory and Builder first:

public class EMailTestStepPanelBuilderFactory implements 
PanelBuilderFactory<EMailTestStep>
{
@Override
public PanelBuilder<EMailTestStep> createPanelBuilder()
{
return new EMailTestStepPanelBuilder();
}

@Override

     public Class<EMailTestStep> getTargetModelItem()
{ return EMailTestStep.class;
}
public static class EMailTestStepPanelBuilder extends EmptyPanelBuilder<EMailTestStep> {
@Override
public DesktopPanel buildDesktopPanel( EMailTestStep modelItem )
{
return new EMailTestStepDesktopPanel( modelItem );
}

@Override public boolean hasDesktopPanel()
{
return true;
}
}
}

 

This is once again pretty straight forward - the EMailTestStepPanelBuilderFactory creates the corresponding PanelBuilder in the createPanelBuilder method, and the EMailTestStepPanelBuilder is implemented as an inner class which simply creates the corresponding EMailTestStepDesktopPanel when asked to. Also, it overrides the hasDesktopPanel() method to return true to let soapUI know that it has this functionality.

Of course we need to create the actual EMailTestStepDesktopPanel returned by the PanelBuilder:

public class EMailTestStepDesktopPanel extends ModelItemDesktopPanel<EMailTestStep>
{
public EMailTestStepDesktopPanel( EMailTestStep modelItem )
{
super( modelItem );
}
}

 

No frills for now - very simple - we’ll spice it up further down the line.

Just as for the TestStep Factory, we need to add this PanelBuilder Factory to our email-teststep-factories.xml file, which now contains two factories:

<?xml version="1.0" encoding="UTF-8"?>
<tns:soapui-factories xmlns:tns="http://eviware.com/soapui/config">
    <tns:factory id="EMailTestStep"
   factoryType="com.eviware.soapui.impl.wsdl.teststeps.registry.WsdlTestStepFactory"
   factoryClass="soapui.demo.teststeps.email.EMailTestStepFactory"/>   

    <tns:factory id="EMailTestStepPanelBuilder"
   factoryType="com.eviware.soapui.model.util.PanelBuilderFactory"
   factoryClass="soapui.demo.teststeps.email.EMailTestStepPanelBuilderFactory"/>   
</tns:soapui-factories>

Great! Package this up and restart soapUI - the soapUI log will now contain:

INFO  [DefaultSoapUICore] Adding factories from [C:\..\email-teststep-factories.xml]
INFO [DefaultSoapUICore] Adding factory [class soapui.demo.teststeps.email.EMailTestStepFactory]
INFO [DefaultSoapUICore] Adding factory [class soapui.demo.teststeps.email.EMailTestStepPanelBuilderFactory]

Now double-clicking on one of our Email TestSteps will open an empty window:

And in the bottom left you can see the default table of properties created by the base EmptyPanelBuilder class. Try changing the name of the TestStep there and watch what happens to the title in the opened panel. Fabulous!

3. Implement configuration handling

Now that we have the core plumbing of our TestStep in place let’s add some actual logic. First we need to define the properties that our TestStep will use:

  • Subject - the subject of the email message to send
  • Message - the actual message content
  • Server - which SMTP server to use
  • MailFrom - the from address
  • MailTo - and the to address

(Of course this could be extended with many more - I'll leave that to you).

We also need to add code for reading / writing these properties from/to the underlying XMLBeans configuration object, main points to adding this are as follows:

private String subject;
private String message;
private String server;
private String mailTo;
private String mailFrom;



private void readConfig( TestStepConfig config )
{
XmlObjectConfigurationReader reader = new XmlObjectConfigurationReader( config.getConfig() );
subject = reader.readString( "subject", "" );
  message = reader.readString( "message", "" );
server = reader.readString( "server", "" );
mailTo = reader.readString( "mailTo", "" );
mailFrom = reader.readString( "mailFrom", "" );
}

private void updateConfig()
{
XmlObjectConfigurationBuilder builder = new XmlObjectConfigurationBuilder();
builder.add( "subject", subject );
builder.add( "message", message );
builder.add( "server", server );
builder.add( "mailTo", mailTo );
builder.add( "mailFrom", mailFrom );
getConfig().setConfig( builder.finish() );
}

public String getSubject()
{
return subject;
}

public void setSubject( String subject )
{
String old = this.subject;
this.subject = subject;
updateConfig();
notifyPropertyChanged( "subject", old, subject );
}

This is pretty straight-forward:

  • We define the properties as private fields
  • We add a "readConfig" method that is used to read the values in the configuration into our properties
  • We add an "updateConfig" method that saves all properties back to the underlying XMLBeans configuration object
  • We call this updateConfig method in all property setters before notifying property changes.

4. Add UI

Of course there wouldn't be much fun if we couldn't edit these fields in the DesktopPanel that we created previously - I'll save you the code (download it below) for now - the result is as follows:

This is probably the messiest code to get to grips with as it uses a lot of internal soapUI utility classes for building Swing interfaces (forms, etc) – you are of course in no way forced to use those, if you prefer to use your own Swing components you are free to do just that (or how about embedding JavaFX 2?).

5. Implement run method

Almost there now - we just need to add the actual code for sending the email to our run method in the TestStep:

public TestStepResult run( TestCaseRunner testRunner, TestCaseRunContext context )
{
WsdlTestStepResult result = new WsdlTestStepResult( this );
result.startTimer();
try
{
Properties props = System.getProperties();
props.put( "mail.smtp.host", context.expand( server ) );

Session session = Session.getDefaultInstance( props, null );
Message msg = new MimeMessage( session );
msg.setFrom( new InternetAddress( context.expand( mailFrom ) ) );
msg.setRecipients( Message.RecipientType.TO, InternetAddress.parse(
context.expand( mailTo ), false ) );
msg.setSubject( context.expand( subject ) );
msg.setText( context.expand( message ) );
msg.setHeader( "X-Mailer", "soapUI EMail TestStep" );
msg.setSentDate( new Date() );

Transport.send( msg );
result.setStatus( TestStepStatus.OK );
}
catch( Exception ex )
{
SoapUI.logError( ex );
result.setError( ex );
result.setStatus( TestStepStatus.FAILED );
}

result.stopTimer();
return result;
}

This is actually very straight forward javamail code – fortunately soapUI already includes the required libraries (it needs them for mime attachment handling). Now if we send the above mail by running the TestCase we get the following in the log:

and eventually the following in our mailbox:

Sweet :)

One thing to note is that we used context.expand( <field> ) to assign the corresponding properties on the MimeMessage object; this expands any property-expansions in the corresponding values, thus effectively adding property-expansion support to all fields in the TestStep. This does come at a small price though; the TestStep needs to implement the PropertyExpansionContainer interface which is used by the refactoring engine in soapUI to update property-expansions when properties or contracts are updated. Fortunately this interface only mandates one method:

public PropertyExpansion[] getPropertyExpansions()
{
List<PropertyExpansion> result = new ArrayList<PropertyExpansion>();
result.addAll( PropertyExpansionUtils.extractPropertyExpansions( this, this, "subject" ) );
result.addAll( PropertyExpansionUtils.extractPropertyExpansions( this, this, "message" ) );
result.addAll( PropertyExpansionUtils.extractPropertyExpansions( this, this, "server" ) );
result.addAll( PropertyExpansionUtils.extractPropertyExpansions( this, this, "mailTo" ) );
result.addAll( PropertyExpansionUtils.extractPropertyExpansions( this, this, "mailFrom" ));
return result.toArray( new PropertyExpansion[result.size()] );
}

This is easy to implement thanks to a bunch of internal utility methods used for extracting the PropertyExpansions in our TestStep.
Let's put the property-expansion support to test:

This delivers the following mail when running the TestCase:


Finally let's check how errors are handled - enter a bogus mailHost address and run the TestCase. You should get the following:

6. Wrap-up

That's it! We've created a simple Email TestStep showing how easy it is to extend soapUI with new functionality. Let's recap:

- We created a custom WsdlTestStepFactory which provides our new EMailTestStep

  • EmailTestStepFactory.java
  • EMailTestStep.java

- We created a custom PanelBuilderFactory which provides the PanelBuilder used by soapUI to build the desktop and property panels in the UI

  • EMailTestStepPanelBuilderFactory.java

-We created the EMailTestStepDestopPanel (provided by our EMailTestStepPanelBuilder) that provides the actual UI for the EMail TestStep

  • EMailTestStepDesktopPanel.java

-We created two configuration files - one for registering these two factories and one for the right-click menu of the TestStep

  • email-teststep-factories.xml
  • email-teststep-actions.xml

-We added an icon for the TestStep to the root folder of soapUI

  • email.png

That makes seven files all in all – not too bad – when packaging we need to:

  • compile the java files into a jar that we drop into the soapUI\ext folder
  • put the factories xml file in the soapUI\factories folder
  • put the actions xml file in the soapUI\actions folder
  • put the png in the root folder

An obvious improvement in soapUI would be to support a "plugin-file" format which packs all these into just one file - something for a future release. :)

Next Steps

This was obviously a very basic example; a lot can be improved:

  • Add more configuration possibilities for the actual email; attachments, multiple email addresses, etc.
  • Add a Run button to the panel to allow instant sending of emails.
  • Add support for sending HTML emails.
  • Add support for a script that decides if the email should be sent at all
  • etc...

Download the zip for the source files, configuration files and email.png icon and use it as an inspiration to create your own TestSteps - and please don't hesitate to mail any TestSteps you create to us so we can publish them on the soapUI website for everyone to enjoy and use!

Also please download the latest nightly build to have all the latest fixes (some related to icon handling in the above example) - and don’t hesitate to get in touch in the forum if you have any questions or issues.

As always - Thanks for your time!

/Ole
SmartBear Software

Share this blog post:
  • Facebook
  • DZone It!
  • Digg It!
  • Del.icio.us
  • Reddit
  • Twitter
  • Email It!

Comments  9

Gravatar
  • Mike 02/08/2012 14:17 PM

    I had to put the image file into the /bin directory rather than the root for the icon to appear in soapUI.

     
    Gravatar
  • Ole Lensmar 02/09/2012 04:36 AM

    Thanks for pointing that out - you are totally correct :-)

    The upcoming release will support a plugin-format that allows you to bundle all files related to the plugin in one jar (classes, configs, images) - making it much easier to package and distribute plugins.. 

    cheers!

    /Ole
    SmartBear Software

     
    Gravatar
  • Mike 02/10/2012 15:36 PM

    Any tips on creating a test step that is configured only by a dialog, like the Delay step? I've tried looking at the code that is visible for that step but I can't work out where the dialog is created.

     
    Gravatar
  • Ole Lensmar 02/13/2012 07:12 AM

    Hi Mike,

    The delay step is configured through the SetWaitTimeAction action registered in the soapui-actions.xml resource:

    <tns:actionGroup name="DelayStep Actions" id="WsdlDelayTestStepActions"
    default="SetWaitTimeAction"
    class="com.eviware.soapui.impl.wsdl.actions.teststep.WsdlTestStepSoapUIActionGroup">
    <tns:actionMapping actionId="SeperatorAction" />
    <tns:actionMapping actionId="SetWaitTimeAction" />
    <tns:actionMapping actionId="SeperatorAction" />
    <tns:actionMapping actionId="ShowOnlineSoapUIHelp"
    keyStroke="F1" param="functional/delaystep.html" />
    </tns:actionGroup>
    <tns:actionGroup name="DelayStep Actions" id="WsdlDelayTestStepActions"
    default="SetWaitTimeAction"
    class="com.eviware.soapui.impl.wsdl.actions.teststep.WsdlTestStepSoapUIActionGroup">
    <tns:actionMapping actionId="SeperatorAction" />
    <tns:actionMapping actionId="SetWaitTimeAction" />
    <tns:actionMapping actionId="SeperatorAction" />
    <tns:actionMapping actionId="ShowOnlineSoapUIHelp"
    keyStroke="F1" param="functional/delaystep.html" />
    </tns:actionGroup>
    <tns:actionGroup name="DelayStep Actions" id="WsdlDelayTestStepActions"
    default="SetWaitTimeAction"
    class="com.eviware.soapui.impl.wsdl.actions.teststep.WsdlTestStepSoapUIActionGroup">
    <tns:actionMapping actionId="SeperatorAction" />
    <tns:actionMapping actionId="SetWaitTimeAction" />
    <tns:actionMapping actionId="SeperatorAction" />
    <tns:actionMapping actionId="ShowOnlineSoapUIHelp"
    keyStroke="F1" param="functional/delaystep.html" />
    </tns:actionGroup>
    <tns:actionGroup name="DelayStep Actions" id="WsdlDelayTestStepActions"
    default="SetWaitTimeAction"
    class="com.eviware.soapui.impl.wsdl.actions.teststep.WsdlTestStepSoapUIActionGroup">
    <tns:actionMapping actionId="SeperatorAction" />
    <tns:actionMapping actionId="SetWaitTimeAction" />
    <tns:actionMapping actionId="SeperatorAction" />
    <tns:actionMapping actionId="ShowOnlineSoapUIHelp"
    keyStroke="F1" param="functional/delaystep.html" />
    </tns:actionGroup>
    <tns:actionGroup name="DelayStep Actions" id="WsdlDelayTestStepActions"
            default="SetWaitTimeAction"
            class="com.eviware.soapui.impl.wsdl.actions.teststep.WsdlTestStepSoapUIActionGroup">
            <tns:actionMapping actionId="SeperatorAction" />
            <tns:actionMapping actionId="SetWaitTimeAction" />
            <tns:actionMapping actionId="SeperatorAction" />
            <tns:actionMapping actionId="ShowOnlineSoapUIHelp"
                keyStroke="F1" param="functional/delaystep.html" />
        </tns:actionGroup>

    the source for this action is here: http://www.soapui.org/apidocs/xref/com/eviware/soapui/impl/wsdl/teststeps/SetWaitTimeAction.html

    If you want to do something similar you need to:
    1) create the corresponding action class
    2) register it in your custom actions.xml file

    Hope this helps!

    /Ole
    SmartBear Software


     
    Gravatar
  • Tavi 02/15/2012 10:18 AM

    Hi, 

    I'm a noob to Soap UI and Eclipse and i have a few questions regarding the example mentioned in the post.
    The EmailTestFactory classes should be in an external library or the classes are part of a custom build of Soap UI?

    If the EmailTestFactory classes are in an external library where can i find the dependencies.

     
    Gravatar
  • Ole Lensmar 02/15/2012 17:13 PM

    Hi!

    the EmailTestStepFactory is part of the custom code for the TestStep that needs to be compiled and put in a jar together with the other classes, which is then put in the soapUI\bin\ext folder.

    Hope this helps!

    /Ole
    SmartBear Software

     
    Gravatar
  •  
    Gravatar
  • Anand 04/26/2012 10:15 AM

    Hi,

    Please find my soapUI logs. After all this was successful, I still can't see the email icon on the bar. Please help

    Thu Apr 26 19:17:35 IST 2012:INFO:Adding [C:\Program Files (x86)\SmartBear\soapUI-Pro-4.0.1\bin\ext\email-test.jar] to extensions classpath
    Thu Apr 26 19:17:35 IST 2012:INFO:Adding [C:\Program Files (x86)\SmartBear\soapUI-Pro-4.0.1\bin\ext\postgresql-8.3-604.jdbc3.jar] to extensions classpath
    Thu Apr 26 19:17:35 IST 2012:INFO:Adding [C:\Program Files (x86)\SmartBear\soapUI-Pro-4.0.1\bin\ext\postgresql-8.3-604.jdbc4.jar] to extensions classpath
    Thu Apr 26 19:17:35 IST 2012:INFO:Adding [C:\Program Files (x86)\SmartBear\soapUI-Pro-4.0.1\bin\ext\sqljdbc.jar] to extensions classpath
    Thu Apr 26 19:17:35 IST 2012:INFO:initialized soapui-settings from [C:\Users\anand\soapui-settings.xml]
    Thu Apr 26 19:17:35 IST 2012:WARN:Missing scripts folder [C:\PROGRA~2\eviware\SOAPUI~1.0\bin\scripts]
    Thu Apr 26 19:17:35 IST 2012:INFO:Adding listeners from [C:\Program Files (x86)\SmartBear\soapUI-Pro-4.0.1\bin\listeners\demo-listeners.xml]
    Thu Apr 26 19:17:35 IST 2012:INFO:Adding factories from [C:\Program Files (x86)\SmartBear\soapUI-Pro-4.0.1\bin\factories\email-teststep-factories.xml]
    Thu Apr 26 19:17:35 IST 2012:INFO:Adding factory [class soapui.demo.teststeps.email.EMailTestStepFactory]
    Thu Apr 26 19:17:35 IST 2012:INFO:Adding factory [class soapui.demo.teststeps.email.EMailTestStepPanelBuilderFactory]

     
    Gravatar
  • Ole Lensmar 04/27/2012 05:13 AM

    Hi,

    You will need to upgrade to 4.5 to get this working - some improvements were required (these were in the 4.0.X nightly build at the time of the original post)

    Hope this helps!

    /Ole
    SmartBear Software

     

    Leave a comment:

    1.