This post is about mock objects for unit testing in Dynamics 365 Finance and Operations, using the existing SysMock library.
Mock objects are used to mimic behaviour of real objects to isolate the parts of code you want to test, to simplify test setup, to simulate specific responses and so on.
SysMock is a library for creating mock objects for X++ classes. The only remark about it that I’ve managed to find is SysMock for Dynamics AX 2009, but the library is shipped with F&O too. The current version isn’t the same as the one for AX 2009 – it’s based on CIL, which couldn’t be the case in AX 2009, and classes mentioned in the blog post (SysMockRepository and SysMockExpect) don’t exist anymore, but the capabilities are still there.
Let me begin with an example.
I’m going to test a class called TestedProcess and mock its component: DataProvider class.
class TestedProcess
{
DataProvider provider;
void new(DataProvider _provider)
{
provider = _provider;
}
str run()
{
return strFmt('Name: %1', provider.getName());
}
str run2()
{
return strFmt('Name: %1', provider.getName('x'));
}
}
class DataProvider
{
str getName(str _parameter = '')
{
return 'default';
}
}
A normal test (without any mocking) might look like this:
[SysTestMethod]
public void basic()
{
this.assertEquals('Name: default', new TestedProcess(new DataProvider()).run());
}
Now, let’s say that DataProvider reads data from database, from a web service or so and we want avoid this dependency. All we want is DataProvider returning a particular value as we’re currently testing TestedProcess and not not DataProvider. What we could do is creating a new class inheriting from DataProvider, overriding getName() and returning a fixed value. (Of course, the current DataProvider does the same thing, but it would do something more complex in a real implementation.). Then we would use this new class instead of DataProvider when creating an instance of TestedProcess.
The mock framework allows us to do the same thing, but at runtime. So we don’t need to create our own mock class for DataProvider; we ask the mock framework to create it for us, and we define how it should behave, such as that getName() method will return a particular value.
Look at an example:
using Microsoft.Dynamics.AX.SysMock;
...
[SysTestMethod]
public void mockReturnValue()
{
// Create a DataProvider mock
MockRepository mockRepo = new MockRepository(false);
DataProvider dataProviderMock = mockRepo.CreateMock(classStr(DataProvider), MockObjectType::Strict, false, null, null, null, null, null, null, null, null, null, null);
// Make getName() return 'mock'
dataProviderMock.getName();
MethodCall methodCall = mockRepo.LastMethodCall;
methodCall.Return('mock');
mockRepo.replayAll();
// Run
this.assertEquals('Name: mock', new TestedProcess(dataProviderMock).run());
}
Another scenario is testing how the system behaves when a component throws an exception. We just tell the mock framework to throw “Test exception” error in getName().
[SysTestMethod]
public void mockThrowException()
{
// Create a DataProvider mock
MockRepository mockRepo = new MockRepository(false);
DataProvider dataProviderMock = mockRepo.CreateMock(classStr(DataProvider), MockObjectType::Strict, false, null, null, null, null, null, null, null, null, null, null);
// Make getName() throw an exception
dataProviderMock.getName();
MethodCall methodCall = mockRepo.LastMethodCall;
methodCall.Error('Test exception');
mockRepo.replayAll();
// Run
this.parmExceptionExpected(true, 'Test exception');
new TestedProcess(dataProviderMock).run();
}
Mock objects aren’t used just to provide predefined responses, but also to verify that the mocked object was used in the expected way. For example, our tests above assume that getName() is called once and they would fail if it wasn’t called or was called multiple times.
We can also test that a method was called with particular parameter values:
[SysTestMethod]
public void providerCalledWithRightParam()
{
// Create a DataProvider mock
MockRepository mockRepo = new MockRepository(false);
DataProvider dataProviderMock = mockRepo.CreateMock(classStr(DataProvider), MockObjectType::Strict, false, null, null, null, null, null, null, null, null, null, null);
// Set an expectation of getName() to be called with a particular parameter value
dataProviderMock.getName('x');
mockRepo.replayAll();
// Run
new TestedProcess(dataProviderMock).run2();
}
If the method was called with a different parameter value, the test would fail with DataProvider.getName(x); Expected #0, Actual #1.
As you can see, the SysMock works and can spare us creation of a custom mock classes, at least when we don’t need any extra logic there. And of course, I haven’t demonstrated all the features it has.
But there are problems too.
One is the lack of documentation and, most likely, support.
I also miss some features that I consider important. For example, I didn’t find a way to set a return value and ignore input parameters (maybe I missed something). I do it often in my mock objects – for example, I have a method returning a file content for a file ID and a mock returns a static text regardless of the ID. Another thing to improve is the message like Expected #0, Actual #1 – this isn’t enough information to understand what actually failed.
And the library itself doesn’t look prepared to be used in F&O. The file (PackagesLocalDirectory\bin\Microsoft.Dynamics.AX.SysMock.dll) does exist in development environments (both CHE and UDE), but there is no reference in AOT. You can add it, of course. After I did it, I noticed strange behavior of compilation:
- In the code editor, everything looks correct. I’m getting code completion, the editor complains about wrong code, accepts correct code.
- Project build fails with The “ResolveAssemblyReference” task failed unexpectedly. System.NullReferenceException…. And there are warnings like The primary reference “Microsoft.Dynamics.AX.SysMock, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null” could not be resolved because it was built against the “.NETFramework,Version=v4.8” framework. This is a higher version than the currently targeted framework “.NETFramework,Version=v4.7.2”. Installing .NET Framework 4.8 and using it in TargetFrameworkVersion of the project caused more problems than it solved, so I let it be.
- Model build works and tests can be executed afterwards.
For this research, I always built the whole model before running tests (and then confirmed that I wanted to run tests from the last successful build), but it would be inconvenient for real work.
Ultimately, I see SysMock as a great proof of concept, but I’m not too keen to use it in its current form. If the problem with the project build gets resolved (which probably wouldn’t be too difficult for Microsoft), I’d use SysMock for some simpler scenarios. But it can’t cover all my needs and I’d love to be able to enhance its logic.
One could use SysMock as an inspiration and write a new mocking framework, but while the library isn’t huge, it would still be quite a lot of work. Simply copying the decompiled code could be a problem from the perspective of the intellectual property. The ideal solution would be Microsoft publishing source code of SysMock as open source and letting the community to extend it.