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:
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.
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:
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:
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!):
Hope this helps (now :P )!
I can't argue with Jordi's answer, it's the same basic answer that I'd provide for an XrmUnitTest based unit test. I'll also give two conventions that I've developed to make this type of change more "automagic". These will be XrmUnitTest examples, but you (or @Jordi) could implement them in the FakeXrmEasy framework.
Create a fluent OrderNumber Builder. By default, have it default the Order Number to a particular number, or accept a value even:
public class SalesOrderBuilder : EntityBuilder<SalesOrder>
{
public SalesOrder SalesOrder { get; set; }
public SalesOrderBuilder()
{
SalesOrder = new SalesOrder();
WithOrderNumber();
}
public SalesOrderBuilder(Id id)
: this() { Id = id; }
#region Fluent Methods
public SalesOrderBuilder WithOrderNumber(string orderNumber = null)
{
orderNumber = orderNumber ?? "2";
SalesOrder.OrderNumber = orderNumber;
return this;
}
#endregion // Fluent Methods
protected override SalesOrder BuildInternal() { return SalesOrder; }
}
Then you would call this when you're initializing your test data:
private class Example : TestMethodClassBase
{
// Ids struct is used by the TestMethodClassBase to clean up any entities defined
private struct Ids
{
public static readonly Id<SalesOrder> SalesOrder = new Id<SalesOrder>("7CF2BB0D-85D4-4B8C-A7B6-371D3C6EA37C");
}
protected override void InitializeTestData(IOrganizationService service)
{
new SalesOrderBuilder(Ids.SalesOrder).Create(service);
}
protected override void Test(IOrganizationService service)
{
// Run test
Assert.IsNotNull(service.GetFirst<SalesOrder>().OrderNumber);
}
}
Create a Fluent OrganizationServiceBuilder. Have it default to Adding Order Numbers:
public class OrganizationServiceBuilder : DLaB.Xrm.Test.Builders.OrganizationServiceBuilderBase<OrganizationServiceBuilder>
{
protected override OrganizationServiceBuilder This
{
get { return this; }
}
#region Constructors
public OrganizationServiceBuilder() : this(TestBase.GetOrganizationService()) {}
public OrganizationServiceBuilder(IOrganizationService service) : base(service) { WithSalesOrderNumbersDefaulted(); }
#endregion Constructors
#region Fluent Methods
private static int _salesNumber = 1;
public OrganizationServiceBuilder WithSalesOrderNumbersDefaulted() {
WithFakeCreate((s, e) =>
{
if (e.LogicalName == SalesOrder.EntityLogicalName && e.GetAttributeValue<string>(SalesOrder.Fields.OrderNumber) == null)
{
_salesNumber++; //Use Interlocking if thread safe is required
e[SalesOrder.Fields.OrderNumber] = _salesNumber;
}
return s.Create(e);
});
return this;
}
#endregion Fluent Methods
}
Then your test would just wrap that when you create it:
private class Example : TestMethodClassBase
{
// Ids struct is used by the TestMethodClassBase to clean up any entities defined
private struct Ids
{
public static readonly Id<SalesOrder> SalesOrder = new Id<SalesOrder>("7CF2BB0D-85D4-4B8C-A7B6-371D3C6EA37C");
}
protected override void InitializeTestData(IOrganizationService service)
{
service = new OrganizationServiceBuilder(service).WithSalesOrderNumbersDefaulted().Build();
service.Create(new SalesOrder());
}
protected override void Test(IOrganizationService service)
{
// Run test
Assert.IsNotNull(service.GetFirst<SalesOrder>().OrderNumber);
}
}
Using either of these options will allow you to easily specify you want the auto-order-number defaulted, without you having to default it in every test. And if you add it to you test base class, you would have it set automatically.
In response to OP's Update, unit testing a method that creates the entity.
Use the Fluen tOrganization Builder like so:
private class Example : TestMethodClassBase
{
protected override void Test(IOrganizationService service)
{
service = new OrganizationServiceBuilder(service)
.WithSalesOrderNumbersDefaulted()
.Build();
// Execute Function for test
var id = Example.ValidateAndCreateOrderAndDetail(service);
Assert.IsNotNull(service.GetEntity<SalesOrder>(id).OrderNumber);
}
}
When your ValidateAndCreateOrderDetail method runs, any SalesOrder that gets created will have a SalesOrder Number populated.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With