Posted by: Stephen Oakman | July 15, 2010

A quick look at NSubstitute

For a side project I’ve started to use NSubstitute which is a new mocking framework for .net. We currently use Moq for pretty much all of our projects but NSubstitute looks really nice so I thought I would try it out.

The syntax is really clean, with the call to Substitute.For<IThingToMock>() returning an IThingToMock. This means you can create your mock and then pass it in to your class you are testing without having to call a .Object like Moq does.

But where it really helped me out was with a particular situation. I’m dabbling with database migrations at the moment where I’m using a Migration attribute which I scan for. When scanning for these migration attributes I wanted to make sure that they were stored in order so in Moq I have this test:

var migrationStoreMock = new Mock<IMigrationStore>();
var storedMigrations = new List<IMigration>();
migrationStoreMock.Setup(s => s.StoreMigration(It.IsAny<IMigration>()).Callback<IMigration>(c => storedMigrations.Add(c));
var migrator = new Migrator(migrationStoreMock.Object);
migrator.Scan<TypeThatHasTestMigrations>();
storedMigrations[0].Version.ShouldEqual(1);
storedMigrations[1].Version.ShouldEqual(2);
storedMigrations[2].Version.ShouldEqual(3);
storedMigrations[3].Version.ShouldEqual(4);

What has always bugged me with this is that it ‘sort of’ follows the Arrange Act Assert pattern, but the callback to add to a list is really arranging ready for the asserts – so it really reads Arrange, Prepare For Assert, Act, Assert. Also, the assert isn’t on the mock itself, so there’s some indirection on what I’m asserting on.

Now for the NSubstitute equivalent:

var migrationStore = Substitute.For<IMigrationStore>();
var migrator = new Migrator(migrationStore);
migrator.Scan<TypeThatHasTestMigrations>();
migrationStore.Received().StoreMigration(Arg.Is<IMigration>(x => x.Version == 1));
migrationStore.Received().StoreMigration(Arg.Is<IMigration>(x => x.Version == 2));
migrationStore.Received().StoreMigration(Arg.Is<IMigration>(x => x.Version == 3));
migrationStore.Received().StoreMigration(Arg.Is<IMigration>(x => x.Version == 4));

With this version there’s a much clearer Arrange Act Assert flow going on. The assert is on the mock itself and doesn’t involve a lambda call to achieve this (which is quite nice). The other benefit is that there’s no .Object call when passing the mock into the class under test.

What may seem noisy is the Arg.Is predicate – but that for me is ok. What’s more of an annoyance for me is I’ve lost the ShouldEqual.

But the fact that I can just call into the mock, through the Received extension method and then use it in a more natural way is very appealing.

It is early days for NSubstitute, with some of the messages it reports back needing work but that’s a known issue and for something in so early a stage it’s a very minor point – and one that I would love to contribute to resolving.

Advertisements

Responses

  1. I love the syntax. Definitely better than the nested lambda expressions you get with AssertWasCalled(x=>x.Call(Arg.Matches(…))) in RhinoMocks.

  2. mock.Loaded += Raise.Action(); //Much nicer than RhinoMocks or Moq.

  3. Thanks for the feedback. 🙂

    If you need to stick with the callback approach then NSub can also do this using the When extension method (or WhenForAnyArgs to ignore the specific arguments), but as you’ve pointed out the syntax isn’t as nice.

    Definitely would welcome contributions. If you’d like to help polish up the exception messages that would be an awesome start! 🙂 Send me an email if you need any info, or post to the group (http://groups.google.com/group/nsubstitute).

    Cheers,
    David

  4. Moq, please for the love of God NO.Object();


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: