Previous posts in this sequence on Getting Started with Xamarin.Forms:
Multi-Targeting in Visual Studio, SDK Versions, MvvmCross, Authenticating with ADAL
Refactoring MvvmCross and Services, Effects, Navigation with MvvmCross
Presentation Attributes in MvvmCross, Resources and Styles, Resource Dictionaries
Platform Specific Resources with OnPlatform, Device Customization using OnIdiom
Unit Testing with xUnit and Moq
One of the hardest aspects of testing is implementing and executing platform specific unit tests. As I was putting together a post covering platform specific testing I got side tracked looking at the use of Moq, which I posted about previously. On closer inspection of the csproj of the xUnit test project that I created as part of that post, the target framework is set to netcoreapp2.1. In order to test platform specific features I need to adjust the csproj to be multi-targeted covering android, ios and uwp. By making the test project multi-targeted the code can access platform specific APIs in order to be able to verify the platform specific code in our Core library.
If we compare the target frameworks with those used by the Core project of our application, we can see that we have netcoreapp2.1 instead of netstandard2.0. In order to take advantage of the xUnit test runner, the test project does need to target netcoreapp2.1. We don’t really need to add netstandard2.0, since we don’t have a test runner that is only based on netstandard2.0 and our application never actually runs as a .NET Standard application (i.e. it runs as an iOS, Android or UWP application)
After changing the target frameworks the test project doesn’t build for a couple of reasons:
- The Visual Studio xUnit test runner isn’t compatible with the platform specific target frameworks
- xUnit has a platform specific test running that needs to be referenced. I’ll cover the platform specific test runners in future posts
The csproj of the test project should be updated to selectively include package references:
<ItemGroup Condition=" '$(IsNetCoreApp)' != 'true' ">
<PackageReference Include="xunit.runner.devices" Version="2.4.48" />
<PackageReference Include="UnitTests.HeadlessRunner" Version="2.0.0" />
<ItemGroup Condition=" '$(IsNetCoreApp)' == 'true' ">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PackageReference Include="xunit" Version="2.4.0" />
Even after changing the package references to make them conditional, there were issues referencing the stable release of Moq. Unfortunately the current release of Moq uses some features around reflection that aren’t supported by all platforms, and whilst it’s compatible with netcoreapp2.1, it’s not compatible with netstandard2.0. I went looking for other mocking options and almost immediately came across the work being done on the next iteration of Moq – this is a major change that does more of the heavy lifting at compile time, rather than dynamically at runtime.
At the time of writing, the next release for Moq is currently available only as source code from https://github.com/moq/moq
Once the source code has been built, the assemblies can be added as a direct reference to the test project.
<ItemGroup Condition="'$(DesignTimeBuild)' == 'true'">
<Analyzer Include="..\Library\Moq\Stunts.dll" />
<Analyzer Include="..\Library\Moq\Stunts.Sdk.dll" />
<Analyzer Include="..\Library\Moq\Roslyn.Services.Test.Utilities.dll" />
<Analyzer Include="..\Library\Moq\Stunts.CodeAnalysis.dll" />
<Analyzer Include="..\Library\Moq\Stunts.CodeFix.dll" />
<Analyzer Include="..\Library\Moq\netstandard.dll" />
<Analyzer Include="..\Library\Moq\Moq.Sdk.dll" />
<Analyzer Include="..\Library\Moq\Moq.CodeAnalysis.dll" />
<Analyzer Include="..\Library\Moq\Moq.CodeFix.dll" />
In addition to assembly references, the Moq libraries are also added as Analyzers – this is to allow for the design time generation of code. Which brings me to the next step which is to adjust our test case to make use of the new Moq syntax. Currently the new version of Moq requires the manual inclusion of Mock.cs and Mock.overloads.cs (content files in the output folder that are generated by building the Moq source code) which include a bunch of helper methods that you’ll need.
The basic process for testing with a mock hasn’t changed – you still need to create and setup your mock entities, invoke a series of methods and then verify actual vs expected output:
public async Task AuthenticateTest()
var credentialsMock = Mock.Of<ICredentialService>();
#pragma warning disable CS4014 // Don't await - we're calling Authenticate so that we can train the Mock to return a specific value
#pragma warning restore CS4014
var mainViewModel = new MainViewModel(
I’ve bolded the lines that are worth reviewing in the amended test method:
- Where previously we created a new instance of Mock<ICredentialService>, now we’re using the method Mock.Of<ICredentialService>.
- Previously we called Setup to define the mocked behaviour. Now, we call the actual method we want to mock and then use the Returns method to define what the behaviour should be. Since we actually want to mock the Task that is returned by the Authenticate method, it’s important that we don’t attempt to await it. Hence the #pragma statements
This code will not currently compile, showing an error stating that there’s currently no mock implementation on ICredentialService. This error is generated by the Moq analysers we added to the csproj.
Expanding the help tooltip
Following the tooltip suggestion we can go ahead and create the mock for the ICredentialService. This will be added to a “Mocks” folder and appropriately named ICredentialServicesMock.
With that we can go ahead and run the test case.
In this post we’ve taken a quick look at getting started with the next version of Moq. We’ll take this further in the next post to cover running platform tests.