How To Unit Test SSAS Stored Procedures Using A Poor Man's Inversion Of Control Container
At some point I was involved in developing some more-than-trivial code to be run as a Stored Procedure in SSAS. This was “more-than-trivial” because there were some separate components that needed to be coded independently and properly unit tested. Notice the use of “properly” in the previous sentence. This entails the correct isolation of dependencies for the objects being tested, so we stay in the realm of unit testing as opposed to integration testing. To do this we need some basic Inversion of Control plumbing put in place, so we can isolate these dependencies during unit testing, but still use the correct objects at run-time. This comes with the added bonus that our code gets naturally decoupled, so we can change or swap implementations of specific objects in the future.
Unfortunately SSAS is far from being IoC friendly, due to the fact it has no “place” to register a man-in-the-middle IoC Container for the purposes of instantiating specific types. This is in contrast with other worlds out there, like ASP.NET. Still, if you accept this, you can still savor the goodies of decoupling your code (well, sorta) while having decent unit testing all around. So, not the richest experience but it does the job. This is Poor Man’s Inversion of Control.
The Container
First thing is to get an IoC Container going. While there are numerous implementations out there, they usually involve a big number of DLLs needing to be installed and bundled with your program. While this is OK for standard applications, having to install and maintain a high number of external DLLs in your SSAS instance only adds complexity unnecessarily, as one doesn’t usually need the full capabilities of a mature container to test simple SSAS Stored Procedures and their only-slightly-more-than-trivial components.
The Container Interface
Just because a container sits at the top and handles resolution of dependencies doesn’t mean it can’t have its implementation replaced at some point. We might at some point decide that we need to grab type registrations out of a file somewhere. We never know of course, so we can’t make assumptions, but we can give ourselves some very cheap leeway for future changes by decoupling the container implementation from its interface, just like we would do with everything else. Below is the basic interface that we will be implementing.
public interface IContainer
{
/// <summary>
/// Registers an implementation type in the container.
/// </summary>
/// The type to be resolved. Typically an Interface type.
/// The type to be instantiated. Typically an Implementation type.
void Register<I>();
/// <summary>
/// Clears all registrations.
/// </summary>
void Clear();
/// <summary>
/// Get all registrations.
/// </summary>
/// The list of registrations.
KeyValuePair[] GetRegistrations();
/// <summary>
/// Basic Resolve method.
/// </summary>
/// The Type to be resolved. Typically an Interface.
/// The resolved object.
I Resolve<I>();
}
They’ll be used like so:
Register()
: This will be used to add type registrations to the container.Clear()
: This will be used to clear all type registrations on the container. To be fair, I’ve never seen this being used even in common applications, but it never hurts to have it just for neatness’ sake.GetRegistrations()
: This will expose the list of registrations. While not really needed, this will aid in unit testing to help verify that registrations themselves are working properly.Resolve<I>()
: This is the meaning of life of an IoC Container. This method will take a dependency type, resolve it, and create an instance of it, ready to be used.
The Container Implementation
Now it’s time to implement the container itself. We’ll through the bits of code, one by one.
First of all, our container will implement the interface defined previously, so we need to declare it:
public class Container : IContainer
{
// (...)
}
We will need some place to put all type registrations too. For our purposes, a Generic Dictionary should do the trick, as what we want is a list of pairs of Interface/Implementation.
/// <summary>
/// Holds all the registrations available.
/// </summary>
private Dictionary mRegistrations = new Dictionary();
We will need some way to register these pairs at some point, so let’s add that in now. Note the locking mechanism there. Racing conditions here will probably never happen, but you never know who’ll be using your code in the future, so might as well make it neat.
/// <summary>
/// Registers an implementation type in the container.
/// </summary>
/// The type to be resolved. Typically an Interface type.
/// The type to be instantiated. Typically an Implementation type.
public void Register<I>()
{
// lock the dictionary just to ensure we don't add duplicates
lock (mRegistrations)
{
// check to see we don't have the type already registered
if (mRegistrations.ContainsKey(typeof(I)))
{
throw new ArgumentException(String.Format("Type '{0}' is already registered.", typeof(I).FullName));
}
// register the type now
mRegistrations.Add(typeof(I), typeof(T));
}
}
Let’s not forget to add a way to clear those registrations, just in case we need it. Fortunately, this is rather trivial:
/// <summary>
/// Clears all registrations.
/// </summary>
public void Clear()
{
mRegistrations.Clear();
}
To aid unit testing of the container, we can add a way to peek at the registrations too. We won’t actually want to allow anyone to change them this way, so we’ll be copying the lot into an array before returning them.
/// <summary>
/// Get all registrations.
/// </summary>
/// The list of registrations.
public KeyValuePair[] GetRegistrations()
{
// copy the items to an array, so changes to it do not affect the dictionary
// the pairs are structs themselves so they cloned instead of referenced
return mRegistrations.ToArray();
}
We’re almost done. All that is left is the heart of the IoC Container. We’ll now implement the interface signature for the Resolve()
method. However, because our type registration dictionary holds “types” and not “generic parameters”, we’ll defer the execution to another method that uses those instead. The generic parameters on the interface is, are in fact just there for visual niceness, we don’t really need them.
/// <summary>
/// Basic Resolve method. This only supports regular object instantiation for now (no singleton).
/// </summary>
/// The Type to be resolved. Typically an Interface.
/// The resolved object.
public I Resolve<I>()
{
// resolution is done using type objects themselves
// the generic parameters are just for coding niceness
return (I)Resolve(typeof(I));
}
Now for the heart of the beast. Don’t worry, it’s a small heart in fact. The internal Resolve()
method will take the given type, select its simplest constructor and recursively resolve and instantiate any needed dependencies. Of course, these need to be registered too. It will also check for circular references, so it doesn’t end blowing up the stack if we happen to have these in our code. Of course, if we do have these in code, we need to rethink our strategy before even going into IoC in the first place.
/// <summary>
/// Internal Resolve method. This does the actual work of resolving.
/// Also offers protection against circular references.
/// </summary>
/// The Type to be resolved.
/// The collection of types already under instantiation. This provides protection against circular references.
/// The resolved object.
private object Resolve(Type type, HashSet references = null)
{
// ensure the given type is not null
if (type == null)
{
throw new ArgumentNullException("type");
}
// ensure the given type is not already being instantiated
if (references != null && references.Contains(type))
{
throw new ArgumentException(String.Format("Requested object graph contains a circular reference involving type '{0}'.", type.FullName));
}
// get the registered type out
Type xRegisteredType = null;
if (mRegistrations.TryGetValue(type, out xRegisteredType))
{
// get the constructor with the lowest number of parameters
var xConstructor = xRegisteredType.GetConstructors().OrderBy(x => x.GetParameters().Count()).FirstOrDefault();
if (xConstructor == null)
{
throw new ArgumentException(String.Format("Type '{0}' has no available constructor.", xRegisteredType.FullName));
}
// see if we need to instantiate any parameters
var xTypes = xConstructor.GetParameters().Select(x => x.ParameterType).ToArray();
var xInstances = new object[xTypes.Length];
if (xTypes.Length > 0)
{
// flag the current type as being instantiated already
if (references == null)
{
references = new HashSet();
}
references.Add(type);
// resolve any dependencies
for (int idx = 0; idx < xTypes.Length; ++idx)
{
xInstances[idx] = Resolve(xTypes[idx], references);
}
// unflag the current type as being instantiated
references.Remove(type);
}
// instantiate the requested type now
return xConstructor.Invoke(xInstances);
}
else
{
// no registered type was found so we break here
throw new ArgumentException(String.Format("No type registered for interface '{0}'.", type.FullName));
}
}
The Container Registration
This is where it gets less pleasant. Unfortunately, SSAS does not provide us with any kind of IoC friendly framework, or any other place for us to intersect the creation of class instances for that matter, as you have in other .NET hosts like the ASP.NET pipeline. So we’re forced to make our future classes directly depend on the Container to do type resolution, instead of having the host do that resolution for them. However, we can limit the damage by only exposing the IContainer
interface, instead of the hard class itself, thereby keeping some leeway for future changes by “sorta” decoupling the container implementation from the objects that need it, even if not completely.
Another thing to think about is that we want to avoid the constant, unnecessary instantiation of the Container, as after it is created, it becomes pretty much static. However, if we had used a static class, we would have been unable to write an interface for it. We can compensate for that now by wiring up a static class that:
- Provides us a clean place to handle the type registration, even if in code.
- Holds on to a singleton instance of the Container, as to avoid having to instantiate it multiple times.
- Makes that singleton Container available pretty much everywhere by means of an extension method on
object
, but by exposing its interface as opposed to the implementing type instead.
While this is far from pretty, it still provides a half-baked way to decouple the Container, which is still better than nothing at all.
/// <summary>
/// Provides the registration point for the Poor Man's IoC Container Implementation.
/// This will allow every class to access the single instance of the configured container.
/// Not the scenario, but lack of a proper registration point in SSAS does not allow much better.
/// </summary>
public static class ContainerExtensions
{
static ContainerExtensions()
{
// register the container implementation here
sContainer = new Container();
// register the types here
sContainer.Register();
}
private static IContainer sContainer;
public static IContainer Container(this object target)
{
return sContainer;
}
}
The Procedure
Believe it or not, the hard work has been done. Resolving dependencies in your code in now pretty straightforward, considering the “un-neatness” of not having a fully decoupled IoC Container. The code below provides an example of how you would call upon the container to resolve your dependencies at run-time, while still allowing you to isolate those dependencies during unit testing.
public class MyProcs
{
/// <summary>
/// The parameterless constructor is the one used by SSAS.
/// Poor Man's IoC Resolution will be done here.
/// </summary>
public MyProcs()
{
// resolve dependencies now
mDependency = this.Container().Resolve();
}
/// <summary>
/// This constructor is used for unit testing.
/// It allows injection of mocked dependencies.
/// It is not used by SSAS as SSAS does not support constructors with parameters.
/// </summary>
/// The ISomeInterface implementation to use.
public MyProcs(ISomeInterface dependency)
{
mDependency = dependency;
}
/// <summary>
/// References the implementation of the ISomeInterface being used.
/// </summary>
private ISomeInterface mDependency;
/// <summary>
/// Does some magic on SSAS.
/// </summary>
/// A flat result with some magic applied to it.
public DataTable DoMagic()
{
// use the dependency here
var xResult = mDependency.RunSomeMethod();
// do some magic here with the result
DataTable xTable = new DataTable();
xTable.Columns.Add("SomeColumn", typeof(int));
xTable.Rows.Add(xResult);
return xTable;
}
}
It is used as so:
MyProcs()
: The default constructor will be the one called by SSAS - always. We can’t avoid this unfortunately, it’s just how SSAS works. So we’ll have to call upon the Container right here.MyProcs(ISomeInterface dependency)
: The parametrized constructor will be used for unit testing - and would be the only constructor, if SSAS allowed it. Here, you can inject fake or mock dependencies in order to isolate the functionality being tested, without having any unknown dependent code run.mDependency
: This merely holds on to the provided (or resolved) dependency for future use.DoMagic()
: This is the stored procedure code itself, which you can call through SSAS. Notice this code will then use the dependency as an interface, therefore not worrying about the implementation.
The Unit Test
We now arrive at the main point of this exercise. To allow us to isolate specific functionality for testing. That’s what the “Unit” in “Unit Testing” is all about, isn’t it? Fortunately, having the scaffolding now in place, we can inject dependencies to our hearts content. The code below shows an example of this, with a test that verifies the functionality of the stored procedure, while isolating it from the behavior of its dependency.
[TestMethod]
public void MyProc_Does_Some_Magic()
{
// ARRANGE
ISomeInterface xDependency = new SomeFakeImplementation();
MyProcs xMyProcs = new MyProcs(xDependency);
// ACT
DataTable xResult = xMyProcs.DoMagic();
// ASSERT
Assert.IsNotNull(xResult);
Assert.IsTrue(xResult.Columns.Contains("SomeColumn"));
Assert.AreEqual(1, xResult.Rows.Count);
}
And what is that SomeFakeImplementation
class there, you may ask? It’s nothing more than what it says on the tin: a fake implementation of the ISomeInterface
interface, for the sole purpose of testing, like so:
public class SomeFakeImplementation : ISomeInterface
{
public int RunSomeMethod()
{
return 0;
}
}
Of course, you cam pimp it up and use a proper mocking framework like moq to create these classes on-the-fly.
Thoughts
It would be really cool if SSAS would at some point support a proper entry point for IoC Containers to piggy back on, like in ASP.NET MVC. Or better yet, a centralized place in the .NET Framework itself, maybe at the assembly level, who knows. With the whole Owin/Katana endeavor going on and spreading about, one can only hope.