In Part 1, I gave a quick introduction into the world of TDD. Nothing I did was specific to CRM. During this post, I'll talk more about specific issues with CRM Plugin development.
Let's start by taking a look at the existing code from Part 1:
public class DemoPlugin { }
Notice here that we haven't setup our plugin to implement the IPlugin interface that is required in a CRM plugin. Since we hadn't tested anything around this we hadn't written the production code yet.
Let's update our class to implement the IPlugin interface:
public void Execute(IPluginExecutionContext context)
{
throw new NotImplementedException();
}
We also need to start testing this code. But, what does this code do!? If you recall from Part 1, this code will fire when an account is created or updated and call a
webservice, passing vital information to that
webservice. We're considering vital information the account's Name,
GUID and Account Number. For the sake of this example we don't care what the
webservice does with this information since we're mocking out that interface.
So there are a few vital processes here:
- Retrieve account information from the EntityImage passed in through the plugin context
- Call the webservice with specific information
So now we need to start with writing out test case. The first thing we'll do is mock out the
plugin context and tell
NMock that we'll retrieve the
EntityImage information from it.
I'm going to start here in skipping past a lot of the detail code analysis and things specific to TDD. I want to focus more on details regarding TDD with
CRM Plugins. There are tons of good references out there and just a quick
google search for them will provide tons of good examples.
One thing I will say is that I tend to setup my test cases in a specific format. The top portion sets up any mock objects. The next session sets up all expectations in order in which I expect them to occur in code. Next is the method call that I'm testing and then finally any assertions or, in this case, just verifying that all my expectations were met.
This is my test case expecting that the entity image will be read from the
plugin context:
[Test]
public void Execute_EntityImageIsReadFromContext()
{
Mockery mock = new Mockery();
IWebServiceWrapper webService = mock.NewMock();
IPluginExecutionContext context = mock.NewMock();
DemoPlugin plugin = GetDependencyInjectedPlugin();
DynamicEntity account = new DynamicEntity("account");
account["accountid"] = new Key(Guid.NewGuid());
account["accountnumber"] = "someAccountNumber";
account["name"] = "accountName";
PropertyBag postEntityImages = new PropertyBag();
postEntityImages["postImage"] = account;
Expect.Once.On(context).GetProperty("PostEntityImages").Will(Return.Value(postEntityImages));
plugin.Execute(context);
mock.VerifyAllExpectationsHaveBeenMet();
}
I don't want to dwell into the details here because it's specific to the NMock and NUnit frameworks. But let's cover a few bigger points
First is I create a simple DynamicEntity object that will represent the account record as it's passed into the plugin context. Notice here that I'm putting values in. This will be part of the "happy path" tests that will be written. Later on I'll show you how to test for non-happy-paths.
Next, I create a PropertyBag object which will hold the account object created above.
Finally, I tell the NMock framework (that has mocked out the IPluginExecutionContext object) that I Expect the PostEntityImages property to be read. When it is, I want to return the PropertyBag object I created above.
I'll skip the boring screenshot showing that my test doesn't pass because all the invocations weren't happening.
No, if I just implement one line of code I get a passing test:
public void Execute(IPluginExecutionContext context)
{
DynamicEntity account = (DynamicEntity) context.PostEntityImages["postImage"];
}
So that sure seemed like a lot of work in writing that test case just for just one line of code. I beg you not to look at it like this even though such an observation is inevitable. This is not about writing less code, this is about writing better code and more verifiable code. Later on we may change our code and not realize we accidentally got rid of this one line. Our unit test will show us quickly that we have whereas we'd otherwise build the assembly, register the plugin, go into CRM and update an account before we got an error and a little upfront code in a unit test will save you lots in the end. If I didn't believe this I would be spending all this time blogging about it.
Let's continue, shall we?
We've got a passing test, but first I want to refactor some things. As I mentioned before, refactoring is important in TDD. Not only for your production code but refactoring your test cases is immensely important in having easily maintainable test cases. You'll find yourself often wondering why you're writing test cases when you could just write the code and "be done" and having unmanageable test cases will only exacerbate that.
Refactoring is as much an art as anything. I do not pretend that I'm skillful at it but after some simple refactorings I get the following test cases (I have no need to refactor my plugin yet, except for maybe putting it in a proper file but I'll do that later):
[TestFixture]
public class PluginTestCases
{
private Mockery mock;
private IWebServiceWrapper webService;
private IPluginExecutionContext context;
[SetUp] // executes at the beginning of each test case. Executed only once per TextFixture
public void Setup()
{
mock = new Mockery();
webService = mock.NewMock();
context = mock.NewMock();
}
[Test]
public void New_DefaultDependencyIsCreatedCorrectly()
{
DemoPlugin plugin = new DemoPlugin();
Assert.IsInstanceOfType(typeof (MyWebService), plugin.webService);
}
[Test]
public void New_DependencyInjectionTakes()
{
DemoPlugin plugin = new DemoPlugin(webService);
Assert.AreEqual(webService, plugin.webService);
}
[Test]
public void Execute_EntityImageIsReadFromContext()
{
DemoPlugin plugin = GetDependencyInjectedPlugin();
DynamicEntity account = CreateSampleAccountDEWithPopulatedValues();
PropertyBag postEntityImages = CreatePropertyBagWithAccount(account);
SetupExpectationsThatPostEntityImagePropertyBagIsRead(postEntityImages);
plugin.Execute(context);
mock.VerifyAllExpectationsHaveBeenMet();
}
private DemoPlugin GetDependencyInjectedPlugin()
{
return new DemoPlugin(webService);
}
private void SetupExpectationsThatPostEntityImagePropertyBagIsRead(PropertyBag postEntityImages)
{
Expect.Once.On(context).GetProperty("PostEntityImages").Will(Return.Value(postEntityImages));
}
private PropertyBag CreatePropertyBagWithAccount(DynamicEntity account)
{
PropertyBag postEntityImages = new PropertyBag();
postEntityImages["postImage"] = account;
return postEntityImages;
}
private DynamicEntity CreateSampleAccountDEWithPopulatedValues()
{
// the account entity that fired the update
DynamicEntity account = new DynamicEntity("account");
account["accountid"] = new Key(Guid.NewGuid());
account["accountnumber"] = "someAccountNumber";
account["name"] = "accountName";
return account;
}
}
All I did here was rearrange some code to make it more readable. I can verify this by running all my tests again and seeing that they still all pass. A good refactoring tool like Resharper will greatly help in refactoring and make you much more likely to do it.
A few points here that can be refactored/improved upon:
- The key value for the property bag "postImage" should probably be pulled into either a configurable option or a const value. Configurable is nice but it's probably something you just need in documentation for the implementer of the plugin so that when they register it in the CRM system they know what images to setup the plugin to receive.
- Regarding deployment (this isn't TDD specific, just a point I like to follow), when developing your plugin you will likely be testing against CRM directly (remember, TDD is a development tool, not a testing tool). You'll have to use one of the registration tools. It's a good idea to export your registration settings into an XML file and store that in your project for reference. This can also be included easily then in a deployment package (msi file or otherwise) to ease deployment on the implementer.
That's it for this part. Stay tuned for part 3 where I show the testing of the call to the webservice.
=-}