However, let's add some context to all of this. Let's say we've got a nicely integrated system between CRM and Sharepoint 2007 (MOSS). Our webservice that we've called in Part 3 takes the account information we supplied and determines if a Sharepoint site needs to be provisioned, and if so, creates that site and populates it with some data. Again, for simplicity I'm not considering all the various issues involved with the integration. We're making this plugin very simple. Assume that all the serious logic and code is all in the webservice that we aren't developing here.
This webservice is called and if a site is provisioned it returns a URL that we can store in CRM and provide as a link to the user so they can easily access the Sharepoint site. For our example here we'll assume the site is provisioned and a URL is returned. We now need to update CRM with that information.
First, let's expand our "Happy Path" test to have the Expect clause include a return value from the webservice call:
Expect.Once.On(webService).Method("UpdateAccount").With(account).Will(Return.Value("http://someurl"));
But of course, before this will work at all, we also need to update our method signature in our interface and the implementing class to have a return value:
public class MyWebService : IWebServiceWrapper
{
public string UpdateAccount(DynamicEntity account)
{
return "";
}
}
public interface IWebServiceWrapper
{
string UpdateAccount(DynamicEntity account);
}
At this point we could run our tests again, but there's no point except to see we didn't break the code (from compiling). The test will still pass because all of our assertions (including the "VerifyAllExpectations...") were met.
Now comes the fun part... we need to make sure the test verifies that the CRM webservice is called and sent the correct data.
But wait! We don't have a CRM WebService. Ah, but we do! It's in the plugin context. And, thankfully, it's an interface, so we can mock it easily:
[Test]
public void Execute_HappyPathExecutesAsExpected()
{
DemoPlugin plugin = GetDependencyInjectedPlugin();
DynamicEntity account = CreateSampleAccountDEWithPopulatedValues();
PropertyBag postEntityImages = CreatePropertyBagWithAccount(account);
SetupExpectationsThatPostEntityImagePropertyBagIsRead(postEntityImages);
Expect.Once.On(webService).Method("UpdateAccount").With(account).Will(Return.Value("http://someurl"));
ICrmService crmService = mock.NewMock();
Expect.Once.On(context).Method("CreateCrmService").With(true).Will(Return.Value(crmService));
plugin.Execute(context);
mock.VerifyAllExpectationsHaveBeenMet();
}
We've said that the "context" object will have the CreateCrmService method called with "true" passed in as a parameter and it will return the crmService object we have mocked.
Now comes the somewhat tricky part... we have this DynamicEntity object that represents our account. We want to update that to reflect the new website URL we got back from our custom webservice and then call the CRM webservice with that object. But remember that all of this code we're setting up is irrelevant of the actual execution of the code.
In other words, even though I've laid out my test code to represent what's happening in the production code, this has no meaning in terms of the actual execution. If I setup this DynamicEntity object, setup all the expects, and then later update a property on it to reflect the new URL, the first usages of the DynamicEntity object will have the website url in it. So, I have to create a second instance of the DynamicEntity object that will represent the object with an updated URL.
Let's get some code in front of you and perhaps this will make more sense:
[Test]
public void Execute_HappyPathExecutesAsExpected()
{
DemoPlugin plugin = GetDependencyInjectedPlugin();
DynamicEntity account = CreateSampleAccountDEWithPopulatedValues();
PropertyBag postEntityImages = CreatePropertyBagWithAccount(account);
SetupExpectationsThatPostEntityImagePropertyBagIsRead(postEntityImages);
string resultingUrl = "http://someurl";
Expect.Once.On(webService).Method("UpdateAccount").With(account).Will(Return.Value(resultingUrl));
ICrmService crmService = mock.NewMock();
Expect.Once.On(context).Method("CreateCrmService").With(true).Will(Return.Value(crmService));
DynamicEntity dynamicEntityWithURL = CloneDynamicEntity(account);
dynamicEntityWithURL["new_sharepointUrl"] = resultingUrl;
Expect.Once.On(crmService).Method("Update").With(dynamicEntityWithURL);
plugin.Execute(context);
mock.VerifyAllExpectationsHaveBeenMet();
}
So I've created a clone of the "account" object we used before and set the property on it with the url from our custom webservice. I've cheated here and used a CloneDynamicEntity method I developed for the ABetterCRM library. I then setup the expectation that it will be used in the Update method of our CRM WebService.
Note: I'm assuming here the Account entity in CRM has been expanded to include a string attribute called "new_sharepointUrl"
Run the test, and we get a failed test because not all expectations were called. Excellent. Let's write the production code now:
public void Execute(IPluginExecutionContext context)
{
DynamicEntity account = (DynamicEntity) context.PostEntityImages["postImage"];
string resultingUrl = webService.UpdateAccount(account);
ICrmService crmService = context.CreateCrmService(true);
account["new_sharepointurl"] = resultingUrl;
crmService.Update(account);
}
Excellent. Now, we run our test and... WHAT?! It failed?
"NMock2.Internal.ExpectationException: unexpected invocation of crmService.Update(
Expected:
1 time: crmService.Update(equal to
That's weird! We can double check out code 100 times and it looks right.
The trick here is how CRM's DynamicEntity object and how NMock work together. NMock will use the object's Equals method by default to determine if objects are equal. However, if you read earlier posts of mine you'll see that two DynamicEntity objects with the same values do not equate. So, how do we get around this?
There are a couple of options here. You could create a new class that inherits from DynamicEntity and properly overrides the Equals method (my preference). However, if you run across this for other objects then you'll have to keep overriding. If you ever run across a sealed class, you'll be stuck. Let's see if we can take advantage of the NMock library.
If we take a look at the NMock With method we see one option for the parameter is a Matcher class. So let's create our own version of this. Forgive me for not going into details here, but this technique will depend largely on the mocking library you use and each one has a different process for doing this. I don't want this to be too specific to NMock so I'll just give you the code for the matcher:
public class DynamicEntityMatcher: Matcher
{
private readonly DynamicEntity leftSide;
public DynamicEntityMatcher(DynamicEntity leftSide)
{
this.leftSide = leftSide;
}
public override bool Matches(object o)
{
if (!(o is DynamicEntity))
return false;
DynamicEntity rightSide = (DynamicEntity) o;
if (leftSide.Name != rightSide.Name)
return false;
foreach (Property property in leftSide.Properties)
{
if (!(rightSide.Properties.Contains(property.Name)))
return false;
if (!(PropertiesAreEqual(leftSide[property.Name], rightSide[property.Name])))
return false;
}
return true;
}
private bool PropertiesAreEqual(object leftSideProperty,
object rightSideProperty)
{
if (leftSideProperty == null && rightSideProperty == null)
return true;
if (leftSideProperty == null)
return false;
if (leftSideProperty.GetType() != rightSideProperty.GetType())
return false;
if (leftSideProperty.GetType() == typeof(string))
return (string)leftSideProperty == (string)rightSideProperty;
PropertyInfo[] properties = leftSideProperty.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo property in properties)
{
object leftValue = property.GetValue(leftSideProperty, null);
object rightValue = property.GetValue(rightSideProperty, null);
if (leftValue == null && rightValue != null)
return false;
if (leftValue != null && !leftValue.Equals(rightValue))
return false;
}
return true;
}
public override void DescribeTo(TextWriter writer)
{
writer.Write("DynamicEntities are equal");
}
}
Without going line by line, it first compares the names of the two DynamicEntities, then it compares each of the properties using reflection.
Now, with using this Matcher we get a passed test! Yay!
However, there is something here that is very wrong and will cause huge problems. Recursion. This plugin will be registered on the post-update event of the account object. We just performed an update of the account object. This plugin will cause itself to fire. If we don't do anything to catch this, we will get a stack overflow very quickly.
I will discuss how to handle this in Part 5.
=-}
No comments:
Post a Comment