Working (around) with MSpecs

13 Dec 2011

I just started playing with Machine.Specifications (MSpecs). Overall I am very satisfied with the project but I did ran across some issues - dont get me wrong, it works great but it was more like limitations because I was lame and using Express edition.
My specs are simple - I am trying to create a Dependency Injection Container that does absolutely very little, not as powerful as Unity or any other containers. I dont plan to use it anywhere - just am trying to create one for fun. So the specifications are simple (for now).
 
Mapping Interface to Type, MappingInterfaceToType
» should allow creating instance of type registered

In case of multiple constructors, pick the one with injection attribute, MultipleConstructorScenario
» should create instance whose Value property is 20

Container should support singleton instances, SingletonScenario
» should return the same instance for every invocation

Inner container should be supported, SupportForInnerContainer
» should return registration from parent and itself


Simple right? Those are the output for my specifications. It it still under works but the output itself explains a lot - thats the power of the MSpecs. Business Analysts write requirements, gives specifications - This can be changed - they give us the requirements and we "program" the specifications. I know it appears to be some rewired Unit Testing, but if unit tests can give me a bunch of specifications and tell me what failed, I would be more than happy to use that.


Now lets write a specification from scratch. Look at the following simple C# code.
 public class Singleton { }
public class Transient
{
public Singleton SingletonInstance { get; private set; }
public Transient(Singleton ins)
{
SingletonInstance = ins;
}
}

I would like to register the Singleton class as - singleton and the transient class as - transient. So my specification is, instances of transient whose dependency is a singleton should get the same instance. So that becomes my "Subject"


[Subject("instances of transient whose dependency is a singleton should get the same instance.")]
public class InjectionOfSingletonForTransientResolution
{
}

Now to verify that specification, I need to "establish a context" which is that I need my container to be ready for use.
 Establish context = () =>
{
_builder = new TypeBuilder();
};

Now that context is established, I will just write my specification. I create two instances and both my instances should have the same instance of Singleton.

It Should_Use_The_Same_Instance_when_creating_dependent_Components = () =>
{
var instance1 = _builder.Resolve();
var instance2 = _builder.Resolve();
instance1.ShouldNotEqual(instance2);
instance1.SingletonInstance.ShouldNotBeNull();
instance2.SingletonInstance.ShouldNotBeNull();
instance1.SingletonInstance.ShouldEqual(instance2.SingletonInstance);
};

But you know this would not happen just like that. I have my context and I know what it should do. I need to tell it why it should do that - that specification would pass "because I am registering dependency as singleton and instance as transient". So I add my reasons on why (or when) the specification would pass.

Because I_Am_Making_A_Dependency_Registration_As_Singleton_And_TestSubject_As_Transient = () =>
{
_builder.Register(new Singleton());
_builder.Register();
};

The whole specification would look like shown below.
[Subject("instances of transient whose dependency is a singleton should get the same instance.")]
public class InjectionOfSingletonForTransientResolution
{
static ITypeBuilder _builder;

public class Singleton { }
public class Transient
{
public Singleton SingletonInstance { get; private set; }
public Transient(Singleton ins)
{
SingletonInstance = ins;
}
}

Establish context = () =>
{
_builder = new TypeBuilder();
};

Because I_Am_Making_A_Dependency_Registration_As_Singleton_And_TestSubject_As_Transient =
() =>
{
_builder.Register(new Singleton());
_builder.Register();
};

It Should_Use_The_Same_Instance_when_creating_dependent_Components = () =>
{
var instance1 = _builder.Resolve();
var instance2 = _builder.Resolve();
instance1.ShouldNotEqual(instance2);
instance1.SingletonInstance.ShouldNotBeNull();
instance2.SingletonInstance.ShouldNotBeNull();
instance1.SingletonInstance.ShouldEqual(instance2.SingletonInstance);
};
}

When I first run this my specification failed because my container does not handle that yet.
 instances of transient whose dependency is a singleton should get the same instance., InjectionOfSingletonForTransientResolution
» Should Use The Same Instance when creating dependent Components (FAIL)
Machine.Specifications.SpecificationException: Should be [not null] but is [null]
at Machine.Specifications.ShouldExtensionMethods.ShouldNotBeNull(Object anObject) in d:\BuildAgent-03\work\38fe83de684fd902\Source\Machine.Specifications\ExtensionMethods.cs:line 181
at Analytics.Specifications.Container.InjectionOfSingletonForTransientResolution.<.ctor>b__2() in C:\Users\bhargav\documents\visual studio 2010\Projects\Analytics\Analytics.Specifications\Container\TypeBuilderSpecs.cs:line 159
at Machine.Specifications.Model.Specification.InvokeSpecificationField() in d:\BuildAgent-03\work\38fe83de684fd902\Source\Machine.Specifications\Model\Specification.cs:line 75
at Machine.Specifications.Model.Specification.Verify() in d:\BuildAgent-03\work\38fe83de684fd902\Source\Machine.Specifications\Model\Specification.cs:line 53

I fix my container and now I see my specification pass.

One thing to remember is what ever you are working off - your context - it should be static - otherwise the compilation would fail. when looking at others examples, I had a similar question - so here I am telling you upfront.

Running specifications without leaving Visual Studio - No test runners are required.


All my specifications are in a separate class library. I would like to run the specifications without leaving Visual Studio (btw, I am using Express). I remember once upon a time I could make a class library as a startup project and somehow linked NUnit gui runner with the project. I could not get it done with VC# 2010 Express anymore. May be I am missing something. Anyway the solution was to add the following Post-Build event

image

Once my specifications project it built successfully, it automatically generates a nice output. You can use all command line arguments that mspec supports, for now I only care if they pass or fail, hence the simple one.

Debugging my specifications. It was painful


I tried all different strategies like using System.Diagnostics.Debugger.Break() and what not. ConsoleRunner that comes with mspec (mspec.exe) was crashing complaining that it encountered an user defined breakpoint in the code - yeah that was my intention. Anyway to work around that, I create a console program and in the console application, I added reference to my specifications library and from the Git Hub source code for the mspec.exe (thanks to OSS) the following C# code helped me overcome my limitation of not being able to use R# or TestDriven.Net.
 class ContainerSpecsRunner
{
static void Main(string[] args)
{
//Console.WriteLine(typeof(ITest).Assembly.Location);
Program prog = new Program(new DefaultConsole());
prog.Run(new[] { typeof(ITest).Assembly.Location });
}
}

ITest is a type that was defined in my specifications assembly. That way I need not worry about any arguments or hardcode path of my assembly. By the way, at work you might be killed for doing this, I am just doing it at my personal projects.

Some samples?


Ok the biggest problem I had was that I could not find some real world examples, the GIT HUB structure of the project was confusing as hell. I was hoping to find some examples, but I could not. As much as I like the project, I hate to see so little or almost non-existent guidance for new users. So here are some of my specifications. These are written by me - I am just learning the style of specs so forgive me if they are not what you wanted them to be. I am just trying to help.
public interface ITest{
}

public class Test: ITest{
[Injector]
public Test()
{

}
}

[Subject("Mapping Interface to Type")]
public class MappingInterfaceToType
{
static ITypeBuilder _builder;

Establish context = () =>
{
_builder = new TypeBuilder();
};

Because of = () =>
{
_builder.Register();
};

It should_allow_creating_instance_of_type_registered = () =>
{
var resolvedObject = _builder.Resolve();
resolvedObject.ShouldNotBeNull();
typeof(Test).ShouldEqual(resolvedObject.GetType());
};
}

[Subject("In case of multiple constructors, pick the one with injection attribute")]
public class MultipleConstructorScenario
{
public class Test2 : ITest
{
public int Value { get; private set; }
public Test2(ITest demo)
{
Value = 10;
}

[Injector]
public Test2()
{
Value = 20;
}
}

static ITypeBuilder _builder;
Establish context = () => {
_builder = new TypeBuilder();
};

Because of = () =>
{
_builder.Register();
};

It should_create_instance_whose_Value_property_is_20 = ()=>
{
var instance = _builder.Resolve();
instance.ShouldBeOfType();
(instance as Test2).Value.ShouldEqual(20);
};
}

[Subject("Container should support singleton instances")]
public class SingletonScenario
{
static ITypeBuilder _builder;

Establish context = () =>
{
_builder = new TypeBuilder();
};

Because instance_Is_Registered = () =>
{
_builder.Register(()=>new Test());
};

It should_return_the_same_instance_for_every_invocation = () =>
{
_builder.Resolve().ShouldEqual(_builder.Resolve());
};
}

[Subject("Inner container should be supported")]
public class SupportForInnerContainer
{
static ITypeBuilder _builder;
static ITypeBuilder _child;
Establish context = () =>
{
_builder = new TypeBuilder();
_child = _builder.CreateChildBuilder();
};

Because instance_is_registered_with_child_container = () =>
{
_builder.Register(new int[] { 0, 1, 2 });
_child.Register();
};

It should_return_registration_from_parent_and_itself = () =>
{
var arr = (int[])_child.Resolve();
arr.SequenceEqual(new[] { 0, 1, 2 }).ShouldBeTrue();
_child.Resolve().ShouldBeOfType();
};
}


Would be nice if it can print my "Because" field names


Just like the console runner prints my "It" fields, it would be nicer if my reasons are printed. I will see if i can do that myself to the project and may contribute a little.


I hope the examples are useful. By no means they are perfect but they can get you started. You can see in the example I detailed in the beginning, I had a whole bunch of Should statements = that is plain wrong. Each specification should define one thing - otherwise it would be a big mess. Please look at this great project and I really am in love with MSpec.