Monthly Archives: September 2013

Unit Testing IBM Websphere MQ with Fakes

.Net Projects that target the IBM Websphere MQ objects are often hard to unit test. Even with some amount of effort in isolating all the MQ objects through Dependency Injection and some tweaks around stubbing some of the common MQ classes, it’s easy to get into trouble with NullReferenceExceptions being thrown.

When targeting IBM MQ, there are two separate options: The native libraries (amqmdnet.dll) or the IBM.XMS library. I have found the JMS .Net implementation very problematic and hiding important queue options from the consuming classes, so I use mostly the native libraries and those are the focus of this post.

I won’t cover the basic principles of starting to use fakes, many people have covered that already and MSDN has a very nice series of articles on that. I will just highlight some common utility code and tricks I have learned along the way.

IBM MQ Design considerations

When I’m targeting IBM MQ, there’s a common set of design choices I make, and some of the code samples will reflect these options:

  • I always browse messages first. Only after I have actually done what I need to do with the messages, I do a read on them.
  • I use Rx right after the queues, this is why I always browse first. Once a message is browsed I push it through an IObservable, so that later I can do things like filter, sort, throttle, etc.
  • I use System.Threading Timers to do pooling on the MQ queues. They do a very nice usage of threads and they also allow me to change the pooling frequency at run-time.
  • Although I use several mocking frameworks, I tend to use only one per test class. On the test examples, everything will be going through Fakes, but I can easily argue that Fakes isn’t as mature as Moq or Rhino Mocks when it comes to fluent writing and API productivity.

IBM MQ id fields and properties

One common task around testing MQ objects is playing around with their Ids, either the object Id or other Ids like the correlation Id. These are always byte arrays and most times they are fixed size, so I wrote a nice utility method for creating fixed sized arrays:


private static byte[] CreateByteArray(int size, byte b)
{
var array = new byte[size];
for (var i = 0; i < array.Length; i++)
{
array[i] = b;
}
return array;
}

view raw

gistfile1.cs

hosted with ❤ by GitHub

To assert the different Id fields just use CollectionAssert and use the ToList on both byte arrays.

CollectionAssert.AreEqual(messageId.ToList(), message.MessageId.ToList());

IBM MQ Shims tips and tricks

One of the problems you will see when you start using IBM.WMQ Shims is null references when instantiating some objects. This is easily fixed by overriding the constructors on the Shims:

ShimMQQueueManager.ConstructorStringHashtable = (_, __, ___) => { };
ShimMQMessage.ConstructorMQMessage = (_, __) => { };

Some of the objects in IBM.WMQ have a long inheritance chain. Shims don’t follow this, so for example, if you’re doing a Get on a queue with MQMessage and MQGetMessageOptions, this exists in MQDestination that MQQueue inherits from, so to be able to stub this method you need to write something like this:

ShimMQDestination.AllInstances.GetMQMessageMQGetMessageOptions = (_, message, options) =>
{
    Assert.AreEqual(MQC.MQGMO_NONE, options.Options);
    Assert.AreEqual(MQC.MQMO_MATCH_MSG_ID, options.MatchOptions);
    CollectionAssert.AreEqual(messageId.ToList(), message.MessageId.ToList());
};

Open Queue example with the corresponding tests

Here’s a full example of a method that Opens a Queue for reading and/or writing


/// <summary>
/// Opens this queue. Supports listening and writing options, if setup
/// for listening it will do browse and not really reads from the queue
/// as messages arrive to the queue.
/// </summary>
/// <param name="reading">True if we want to read from this queue, false otherwise.</param>
/// <param name="writing">True if we want to write to this queue, false otherwise.</param>
/// <returns>The Observable where all the messages read from this queue will appear.</returns>
public IObservable<IServiceMessage> OpenConnection(bool reading = true, bool writing = false)
{
// create the properties HashTable
var mqProperties = new Hashtable
{
{ MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_MANAGED },
{ MQC.HOST_NAME_PROPERTY, ConfigurationProvider.MQMessageListenerHostName },
{ MQC.PORT_PROPERTY, ConfigurationProvider.MQMessageListenerPortNumeric },
{ MQC.CHANNEL_PROPERTY, ConfigurationProvider.MQMessageListenerChannelName }
};
// create the queue manager
_queueManager = new MQQueueManager(ConfigurationProvider.MQMessageListenerQueueManagerName, mqProperties);
// deal with the queue open options
var openOptions = MQC.MQOO_INPUT_AS_Q_DEF + MQC.MQOO_FAIL_IF_QUIESCING;
if (reading)
{
openOptions += MQC.MQOO_BROWSE;
}
if (writing)
{
openOptions += MQC.MQOO_OUTPUT;
}
// create and start the queue, check for potential bad queue names
try
{
Queue = _queueManager.AccessQueue(QueueName, openOptions);
}
catch (MQException ex)
{
if (ex.ReasonCode == 2085)
{
throw new ConfigurationErrorsException(string.Format(CultureInfo.InvariantCulture, "Wrong Queue name: {0}", QueueName));
}
throw;
}
if (reading)
{
StartListening();
}
return Stream.AsObservable();
}

view raw

gistfile1.cs

hosted with ❤ by GitHub

And the unit tests that test it


/// <summary>
/// Tests that OpenConnection creates the proper MQQueueManager and accesses the
/// queue with the right set of options.
/// </summary>
[TestMethod]
public void Test_OpenConnection_GoesThrough()
{
const string host = "some random host";
const string channel = "some random channel";
const string manager = "some random manager";
const string queueName = "some random queue";
const int port = 1234;
var startListeningCall = false;
using (ShimsContext.Create())
{
var configShim = new ShimConfigurationProvider
{
MQMessageListenerChannelNameGet = () => channel,
MQMessageListenerHostNameGet = () => host,
MQMessageListenerPortNumericGet = () => port,
MQMessageListenerQueueManagerNameGet = () => manager
};
ShimMQQueueManager.ConstructorStringHashtable = (_, s, options) =>
{
Assert.AreEqual(manager, s);
Assert.AreEqual(host, options[MQC.HOST_NAME_PROPERTY]);
Assert.AreEqual(channel, options[MQC.CHANNEL_PROPERTY]);
Assert.AreEqual(port, options[MQC.PORT_PROPERTY]);
Assert.AreEqual(MQC.TRANSPORT_MQSERIES_MANAGED, options[MQC.TRANSPORT_PROPERTY]);
};
ShimMQQueueManager.AllInstances.AccessQueueStringInt32 = (_, s, options) =>
{
Assert.AreEqual(MQC.MQOO_INPUT_AS_Q_DEF + MQC.MQOO_FAIL_IF_QUIESCING, options);
Assert.AreEqual(queueName, s);
return null;
};
var mqShim = new ShimMQMessageQueue
{
InstanceBehavior = ShimBehaviors.Fallthrough,
ConfigurationProviderGet = () => configShim.Instance,
QueueNameGet = () => queueName,
StreamGet = () => new Subject<IServiceMessage>(),
StartListening = () => { startListeningCall = true; }
};
mqShim.Instance.OpenConnection(false);
Assert.IsFalse(startListeningCall);
}
}
/// <summary>
/// Tests the OpenConnection options in the queue access when the queue is setup to read.
/// It also ensures that StartListening is called if the queue is opened for reading.
/// </summary>
[TestMethod]
public void Test_OpenConnection_ForReading()
{
const string queueName = "some random queue";
var startListeningCall = false;
using (ShimsContext.Create())
{
ShimMQQueueManager.ConstructorStringHashtable = (_, __, ___) => { };
ShimMQQueueManager.AllInstances.AccessQueueStringInt32 = (_, s, options) =>
{
Assert.AreEqual(MQC.MQOO_INPUT_AS_Q_DEF + MQC.MQOO_FAIL_IF_QUIESCING + MQC.MQOO_BROWSE, options);
Assert.AreEqual(queueName, s);
return null;
};
var mqShim = new ShimMQMessageQueue
{
InstanceBehavior = ShimBehaviors.Fallthrough,
ConfigurationProviderGet = () => new ShimConfigurationProvider().Instance,
QueueNameGet = () => queueName,
StreamGet = () => new Subject<IServiceMessage>(),
StartListening = () => { startListeningCall = true; }
};
mqShim.Instance.OpenConnection();
Assert.IsTrue(startListeningCall);
}
}
/// <summary>
/// Tests the OpenConnection options in the queue access when the queue is setup to write.
/// </summary>
[TestMethod]
public void Test_OpenConnection_ForWriting()
{
const string queueName = "some random queue";
var startListeningCall = false;
using (ShimsContext.Create())
{
ShimMQQueueManager.ConstructorStringHashtable = (_, __, ___) => { };
ShimMQQueueManager.AllInstances.AccessQueueStringInt32 = (_, s, options) =>
{
Assert.AreEqual(MQC.MQOO_INPUT_AS_Q_DEF + MQC.MQOO_FAIL_IF_QUIESCING + MQC.MQOO_OUTPUT, options);
Assert.AreEqual(queueName, s);
return null;
};
var mqShim = new ShimMQMessageQueue
{
InstanceBehavior = ShimBehaviors.Fallthrough,
ConfigurationProviderGet = () => new ShimConfigurationProvider().Instance,
QueueNameGet = () => queueName,
StreamGet = () => new Subject<IServiceMessage>(),
StartListening = () => { startListeningCall = true; }
};
mqShim.Instance.OpenConnection(false, true);
Assert.IsFalse(startListeningCall);
}
}
/// <summary>
/// Tests the OpenConnection options in the queue access when the queue is setup to
/// read and write at the same time.
/// It also ensures that StartListening is called if the queue is opened for reading.
/// </summary>
[TestMethod]
public void Test_OpenConnection_ForReadingAndWriting()
{
const string queueName = "some random queue";
var startListeningCall = false;
using (ShimsContext.Create())
{
ShimMQQueueManager.ConstructorStringHashtable = (_, __, ___) => { };
ShimMQQueueManager.AllInstances.AccessQueueStringInt32 = (_, s, options) =>
{
Assert.AreEqual(MQC.MQOO_INPUT_AS_Q_DEF + MQC.MQOO_FAIL_IF_QUIESCING + MQC.MQOO_BROWSE + MQC.MQOO_OUTPUT, options);
Assert.AreEqual(queueName, s);
return null;
};
var mqShim = new ShimMQMessageQueue
{
InstanceBehavior = ShimBehaviors.Fallthrough,
ConfigurationProviderGet = () => new ShimConfigurationProvider().Instance,
QueueNameGet = () => queueName,
StreamGet = () => new Subject<IServiceMessage>(),
StartListening = () => { startListeningCall = true; }
};
mqShim.Instance.OpenConnection(true, true);
Assert.IsTrue(startListeningCall);
}
}
/// <summary>
/// Ensure that opening a connection with a Bad Queue Name will throw a proper
/// <see cref="ConfigurationErrorsException"/>.
/// </summary>
[TestMethod]
[ExpectedException(typeof(ConfigurationErrorsException))]
public void Ensure_OpenConnection_ThrowsBadQueueName()
{
const int nameReasonCode = 2085;
using (ShimsContext.Create())
{
ShimMQQueueManager.ConstructorStringHashtable = (_, __, ___) => { };
ShimMQQueueManager.AllInstances.AccessQueueStringInt32 = (_, __, ___) =>
{
throw new MQException(1, nameReasonCode);
};
var mqShim = new ShimMQMessageQueue
{
InstanceBehavior = ShimBehaviors.Fallthrough,
ConfigurationProviderGet = () => new ShimConfigurationProvider().Instance,
QueueNameGet = () => "something",
StreamGet = () => new Subject<IServiceMessage>(),
};
mqShim.Instance.OpenConnection();
}
}
/// <summary>
/// Ensure that any exception besides Bad Queue Name will be re-thrown
/// and bubble out of the OpenConnection method.
/// </summary>
[TestMethod]
[ExpectedException(typeof(MQException))]
public void Ensure_OpenConnection_ThrowsOthersExceptBadName()
{
using (ShimsContext.Create())
{
ShimMQQueueManager.ConstructorStringHashtable = (_, __, ___) => { };
ShimMQQueueManager.AllInstances.AccessQueueStringInt32 = (_, __, ___) =>
{
throw new MQException(1, 1);
};
var mqShim = new ShimMQMessageQueue
{
InstanceBehavior = ShimBehaviors.Fallthrough,
ConfigurationProviderGet = () => new ShimConfigurationProvider().Instance,
QueueNameGet = () => "something",
StreamGet = () => new Subject<IServiceMessage>(),
};
mqShim.Instance.OpenConnection();
}
}

view raw

gistfile1.cs

hosted with ❤ by GitHub

Recording a Web Performance test from a CodedUI test

On a project that’s well supported with tests, it is very common to have a good suite of automated tests. The two most common frameworks for test automation in the .Net stack are CodedUI and Watin. This article will cover utility code that improves recording a Web Performance test from a CodedUI test to automate the initial recording of the performance test. While it is possible to do the same with Watin, there is less control over the recording process, so I won’t cover Watin in this post.

There are two common tasks while going from a CodedUI into a Web Performance:

  • Find the Browser with the recorder.
  • Control the recording process. Often part of the CodedUI is getting to where we want to do the action, and this process isn’t part of the recording phase.

Finding a browser that is ready for recording

Finding a browser that is able to record is just going through the open browsers and look for the recording toolbar and the recording buttons. If we find them, then we have one and we can use it, otherwise just open a new browser and run the test normally.

Some things to note here:

  • Make sure that you wrap all the code that looks for recording controls in compiler directives. If the CodedUI is looking for these controls and can’t find them, it takes a lot longer to run, doing this as part of a build process will just increase the build time by a great amount.
  • While we are looking for things, keep track of the main buttons, Record and Resume, because we may want to click them later on, as part of scoping the recording process.
  • The method that launches the browser takes a Boolean parameter that allows the browser recorder to be paused at the start of the CodedUI test, instead of the default recording behavior.

The code that handles this:


public static class CodedUIExtensions
{
#if !DO_NOT_FIND_WEBRECORD
private static bool _recording;
private static WinButton _recordButton;
private static WinButton _pauseButton;
#endif
public static BrowserWindow Launch(bool pauseRecording = false)
{
return Launch("main.aspx", pauseRecording);
}
public static BrowserWindow Launch(string path, bool pauseRecording = false)
{
#if !DO_NOT_FIND_WEBRECORD
// Try to find an open browser that is recording to do a web performance recording session
try
{
var recordingBrowser = new BrowserWindow();
recordingBrowser.SearchProperties[UITestControl.PropertyNames.Name] = "Blank Page";
recordingBrowser.SearchProperties[UITestControl.PropertyNames.ClassName] = "IEFrame";
recordingBrowser.Find();
var recordWindow = new WinWindow(recordingBrowser);
recordWindow.SearchProperties[WinControl.PropertyNames.ControlName] = "toolStrip1";
recordWindow.Find();
var toolbar = new WinToolBar(recordWindow);
toolbar.SearchProperties[UITestControl.PropertyNames.Name] = "toolStrip1";
toolbar.Find();
_recordButton = new WinButton(toolbar);
_recordButton.SearchProperties[UITestControl.PropertyNames.Name] = "Record";
_recordButton.Find();
_pauseButton = new WinButton(toolbar);
_pauseButton.SearchProperties[UITestControl.PropertyNames.Name] = "Pause";
_pauseButton.Find();
if (pauseRecording)
{
Mouse.Click(_pauseButton);
recordingBrowser.WaitForControlReady();
}
recordingBrowser.NavigateToUrl(new Uri(path));
_recording = true;
return recordingBrowser;
}
catch
{
}
#endif
// A browser with a session ready to record couldn't be found, so open a new one
var browserWindow = BrowserWindow.Launch(path);
browserWindow.WaitForControlReady();
return browserWindow;
}
}

view raw

gistfile1.cs

hosted with ❤ by GitHub

Controlling the recording process

Besides finding the browser, that are 3 common things that we want, as part of controlling the recording process:

  • Be able to pause the recording process.
  • Be able to resume the recording process.
  • Some applications will spawn multiple windows, so at the end of the test an ALT+F4 is sent to the target app. However in the scope of recording a performance test, we want the browser to stay open, so we can do final adjustments or just stop recording and generate the test.

To accomplish this, just add 3 more methods to the utility class (also with compiler directives to improve test run speeds during builds):


public static void PauseRecording()
{
#if !DO_NOT_FIND_WEBRECORD
if (!_recording) return;
Mouse.Click(_pauseButton);
_pauseButton.WaitForControlReady();
#endif
}
public static void ResumeRecording()
{
#if !DO_NOT_FIND_WEBRECORD
if (!_recording) return;
Mouse.Click(_recordButton);
_recordButton.WaitForControlReady();
#endif
}
public static void CloseWindows()
{
#if !DO_NOT_FIND_WEBRECORD
if (!_recording)
{
Keyboard.SendKeys("%{F4}");
}
#else
Keyboard.SendKeys("%{F4}");
#endif
}

view raw

gistfile1.cs

hosted with ❤ by GitHub