snaits snaits - 3 months ago 26
C# Question

Fake plugin behavior in FakeXrmEasy?

I've been using FakeXrmEasy to write unit tests for the past week or so, and I'm generally happy with how it works. But there's one area where I'm not able to get the mock to work as I want it to.

In the Dynamics CRM installation, there's a plugin running, that sets the Order Number on the Sales Order. Without this, the returned order number's value is always null.

How can i tell the FakeXrmEasy mock to set the ordernumber value? Ideally I'd like to tap into the request pipeline more or less like so:

var context = new FakeXrmEasy.XrmFakedContext();
context.Initialize(TestEntities);

context.TamperWithResults<RetrieveRequest>( x => {
return SetOrderNumber(x);
});

context.GetFakedOrganizationService();

var result = context.Retrieve(...);


I could attempt using the .AddExecutionMock to mock the entire result, but the response in question is used to verify that the sales order was indeed saved with the correct values.

Update - more detailed info
Perhaps I should have gone a little more into detail when asking the question. I've just joined an exisiting project and I'm writing tests to existing code. The failing test is running a function that does this:


  • Validates input

  • Creates sales order

  • Retrieves that sales order

  • Create SalesOrderDetails for each order line

  • Returns a result object containing the order number



Now since the function attempts to save the order, I cannot add it to the context in setup unless I can specify the Guid that will be returned by the Create() call.

Answer

Assuming you are writing a unit test for testing whatever happens after that plugin (the plugin which populates the Order Number), the easiest solution is to initialize a sales order with the OrderNumber in it, which will be used as the precondition. Queries are automatically mocked by default, so that value should be returned. No need to inject anything in the pipeline because of that.

Example:

[Fact]
public void Example_test()
{
var context = new XrmFakedContext();
var service = context.GetFakedOrganizationService();
var salesOrder = new SalesOrder() { Id = Guid.NewGuid(), OrderNumber = 69 }; 
context.Initialize(new List<Entity>() { salesOrder });

//some stuff
//....

//Queries are automatically mocked so any LINQ,FetchXml, 
//QueryExpression or QueryByAttrubute should return the values you had in 
//the context initialisation or as a result of updates / creates during the test execution


var result = context.CreateQuery<SalesOrder>().FirstOrDefault();
Assert.Equal(result.OrderNumber, 69);

}

[EDIT]: If you want to inject the guid after the Create you can use the OutputParameters property for that. Here's an example with the FollowupPlugin.

There are several overloads for plugin execution, that example used to be an "old" one. There is a new general purpose method where you can pass a custom plugin context, where you can inject many properties, including the Outputparameters (look for GetDefaultPluginContext here).

But in general, and back to your original question, if you have many steps like these:

  • Validates input
  • Creates sales order
  • Retrieves that sales order
  • Create SalesOrderDetails
  • Returns a result object containing the order number

There could be many approaches to unit test this stuff, but my personal advice is that, those steps are way too many steps to be included in a single unit test. I'd rather refactor that logic so that it could be unit tested separately, more easily.

I'll resume that to only 3 steps to make it simpler:

  • Creation logic: You create a salesorder record, passing in some attributes.
  • Create plugin: A plugin fires in the create of the salesorder which populates the Order Number.
  • Other stuff: You have some logic afterwards which retrieves a salesorder and based on the OrderNumber (or other attributes) does something.

I would first, refactor that code so that I can test it much more easily, in small chunks. Doing this makes tests simpler to implement and understand & review.

I would create 3 different unit tests for each (at least!):

  • Creation logic: A unit test which doesn't require any input entities (so .Initialize() not needed) and just creates a salesorder entity record. Then Assert it has created whatever attributes you were expecting.
  • Create plugin: A unit test to execute the plugin and make sure it does whatever it is supposed to do
  • If you have any logic running after that, make sure it is refactored so that you can inject any properties / values, and then pass them (like the .OrderNumber) via the .Initialize() method in subsequent unit tests.

Hope this helps (now :P )!

Comments