Substituting Moq for NSubstitute

In light of the recent controversy surrounding the .NET Moq library, many developers are reconsidering their choice of mocking framework for unit testing. The undisclosed inclusion of the proprietary SponsorLink DLL that reportedly extracts internal developer email addresses and uploads them to a remote server has undoubtedly been alarming to many. Beyond the immediate privacy and security concerns, this incident raises several valid questions surrounding data protection and licensing.

As a software developer, it’s always a good practice to stay informed and be proactive when selecting tools, making sure that any third-party libraries you choose are fit for purpose. For those who are looking to move away from Moq, NSubstitute emerges as a viable alternative. NSubstitute is known for its simplicity and features a fluent API interface, making it easy to pick up, especially if you’re already familiar with Moq.

In this article, we will explore how we can substitute Moq for NSubstitute, both manually and in a more automated way.

Some Moq background

On 8th August 2023, Moq version 4.20.0 was released.

Nothing seemed out of the ordinary at first, but it was soon discovered that the release included an embedded ‘SponsorLink’ DLL that had the capability of silently scraping email addresses from your local Git repository and uploading them to an undisclosed online location. The SponsorLink library is a compiled, closed-source DLL which is also obfuscated. Ominous much?!

The issue was quickly resolved the very next day following a public outcry, with SponsorLink being removed from Moq version 4.20.2. However, despite clarifications that have since been made by the library author regarding the inner workings of SponsorLink, many developers have lost their trust in Moq and are looking for an alternative solution. Over the last couple of weeks, there has been quite a rush by many to move away from Moq.

This is unfortunate for Moq, as it remains a great library. However, the desire to migrate to other solutions is understandable and is a priority for many development shops at the present time. With the Moq NuGet package having been downloaded almost half a billion times at the time of writing, the number of people and companies who are considering a migration from Moq to another mocking framework is substantial, to say the least.

If you’re interested in getting more information regarding the background of the Moq controversy, I recommend checking out this GitHub Issue where the problem was originally raised.

Bringing on NSubstitute

While there are several potential alternatives to Moq that could be considered, NSubstitute is perhaps the most obvious replacement and is a well-respected library within the .NET community.

Moq was originally released back in 2007, while NSubstitute was first released in 2010.

NSubstitute aims to provide a friendly syntax and a simpler interface than some of the other .NET mocking libraries and it provides pretty much all of the mocking functionality you are likely to need, regardless of your project size and complexity.

Many developers find that NSubstitute provides a better developer experience than Moq and believe it is a better-designed library. However, this is open to debate and opinions will of course vary amongst individuals. Another potential option is FakeItEasy, which has a different syntax that you may prefer depending on your personal preferences.

In this article, I am focusing on migrating from Moq to NSubstitute, as I find the NSubstitute syntax to be the most natural and easy to read. Additionally, NSubstitute has a great feature set, is a well-established and well-supported library, and has a high degree of trust within the .NET community.

NSubstitute Quickstart

While the aim of this article is primarily to look at how you can migrate your projects from using Moq to using NSubstitute, I want to provide a little bit of information regarding how NSubstitute works before we dive into the migration options.

In the subsections that follow I will cover how to install NSubstitute into your project and I will demonstrate a few comparisons between Moq and NSubstitute to give you a feel for some of the key differences.

Install NSubstitute

If you want to migrate from Moq to NSubstitute, the first thing you’ll need to do is install the NSubstitute NuGet package into your project.

If you’re using Visual Studio, you can install NSubstitute via the Manage NuGet Packages interface, or you can run the following command from the Package Manager Console.

Install-Package NSubstitute

For Visual Studio Code, you can run the above command within the integrated Terminal.

Uninstall Moq

You should make sure to uninstall Moq from your project to keep your dependencies tidy.

Uninstall-Package Moq

Your options for uninstallation are the same as the previous subsection for both Visual Studio and Visual Studio Code.

Comparisons

Now that the NuGet packages have been sorted out, let’s explore some of the key differences between Moq and NSubstitute to help get you up to speed on the syntax.

Basic Mock creation

Let’s start by comparing how we can create basic mocks with Moq versus NSubstitue.

Moq
var mockService = new Mock<IMyService>();
NSubstitute
var substituteService = Substitute.For<IMyService>();

As you can see, the transition for mock creations is quite straightforward.

Stubbing return values

Next, let’s look at how we can stub method return values.

Moq
mockService.Setup(m => m.GetSomething()).Returns("Hello World");
NSubstitute
substituteService.GetSomething().Returns("Hello World");

In this example, the fluent interface of NSubstitute results in slightly simpler code, making the intent very clear.

Matching arguments

If we don’t want our tests to care what argument values are passed when verifying method calls, we can indicate that any value of a specific type can be used via both Moq and NSubstitute.

Moq
mockService.Setup(m => m.DoSomething(It.IsAny<string>())).Returns(true);
NSubstitute
substituteService.DoSomething(Arg.Any<string>()).Returns(true);

The Arg class in NSubstitute is equivalent to the It class in Moq, handling argument matching for stubs and checks.

Checking method calls

Here’s how we can verify that a method invocation took place with both frameworks.

Moq
mockService.Verify(m => m.CallMe());
NSubstitute
substituteService.Received().CallMe();

In Moq, the Verify method verifies that a specific invocation matching the given expression was performed on the mock. In NSubstitute, the Received method serves a similar purpose, checking if the substitute has received a call to a specific method.

Exceptions

How about checking if specific exceptions have been thrown?

Moq
mockService.Setup(m => m.GetSomething()).Throws(new Exception("Error!"));
NSubstitute
substituteService.GetSomething().Throws(new Exception("Error!"));

In this regard, aside from the requirement to call the Setup method in Moq, the transition is almost seamless.

There are lots of other comparisons we could make between the frameworks, but hopefully, what you’ve seen thus far helps to give you a flavour of the key differences.

Moq to NSubstitute migration

One of the questions you may have been asking yourself is whether there is a practical migration path for moving from Moq to NSubstitute, without having to rewrite all of your unit tests one by one.

The good news is that while it’s not practical to offer a completely seamless migration from Moq to NSubstitute, thankfully there are ways to make the transition less painful.

Find and Replace

The first potential migration path is to perform some manual Find and Replace operations.

This GitHub Gist was created by Alberto Monteiro shortly after the Moq incident came to light. The Gist contains a collection of Regex-powered Find and Replace patterns for transforming Moq code to the equivalent NSubstitute syntax.

Note that the Gist has since been migrated to this GitHub repository.

Automation

Shortly after Alberto’s Gist, Chris Davey created the Moqstitute repository. Moqstitute is a simple C# Console application that applies the Find and Replace patterns originally produced by Alberto to the specified directory.

After seeing this, I thought wouldn’t it be nice to do something similar, but bundle it into a single script file that can be executed without any setup or compilation required?

Scripting

I’ve taken things in a slightly different direction by creating a PowerShell script that can make the replacements, provided courtesy of Alberto, and performing the same style of logic as Chris’ C# program, with a few minor tweaks.

The PowerShell script has been included below for your reference. The script is also available on GitHub.

DISCLAIMER: Please back up your files before running the script and ideally, make sure your project is using source control so that you can revert the changes if required. The script will overwrite your files – there is no undo button!

# Attempt to set the base directory from the first argument.
$baseDirectory = $args[0]
 
# If the base directory was not set, default it to the current script directory.
if (-not $baseDirectory) {
    $baseDirectory = Split-Path $MyInvocation.MyCommand.Path
}
 
Write-Host
Write-Host "Moq will be replaced with NSubstitute in the following directory.`n"
Write-Host "$baseDirectory`n"
 
# Ask the user for confirmation to proceed.
$confirmation = Read-Host "Do you want to proceed? (y/n)"
 
Write-Host
 
if ($confirmation -ne 'y') {
    Write-Host "Migration aborted.`n"
    exit
}
 
# Simple array for easy copy and pasting of Find and Replace patterns.
$findReplaceArray = @(
    'using Moq;', 'using NSubstitute;',
    'new Mock<(.+?)>\((.*?)\)', 'Substitute.For<$1>($2)',
    '\bMock<(.+?)>', '$1',
    '(?<!\.)\b(\w+)(\s\n\s*)?\.Setup(Get)?\((\w+) => \4(\.?.+?)\)(?=\.R|\s\n)', '$1$5',
    '\.Get<(.+?)>\(\)\.Setup\((\w+) => \2(\.?.+?)\)(?=\.R|\s\n)', '.Get<$1>()$3',
    '\.Get<(.+?)>\(\)\.SetupSequence?\((\w+) => \3(\.?.+?)\)(?=\.R|\s\n)', '.Get<$1>()$3',
    '(?<!\.)\b(\w+)(\s\n\s*)?\.SetupSequence?\((\w+) => \3(\.?.+?)\)(?=\.R|\s\n)', '$1$4',
    '\.Get<(.+?)>\(\)\.SetupSequence?\((\w+) => \2(\.?.+?)(\)(?!\)))', '.Get<$1>()$3',
    '(?<!\.)\b(\w+)\.Verify\((\w+) => \2(.+?), Times\.(Once(\(\))?|Exactly\((?<times>\d+)\))\)', '$1.Received(${times})$3',
    '(?<!\.)\b(\w+)\.Verify\((\w+) => \2(.+?), Times\.Never\)', '$1.DidNotReceive()$3',
    '(?<!\.)\b(\w+)(\s\n\s*)?\.Setup\(((\w+) => \4(\..?.+?)\))\)\s*\n*\.Throws', '$1.When($3).Throw',
    'It.IsAny', 'Arg.Any',
    'It.Is', 'Arg.Is',
    'MoqMockingKernel', 'NSubstituteMockingKernel',
    'using Ninject.MockingKernel.Moq;', 'using Ninject.MockingKernel.NSubstitute;',
    '\.GetMock<(.+?)>\(\)', '.Get<$1>()',
    '\.Object([\.,;)\s])', '$1',
    '\.Setup(Get)?\((\w+) => \2(\.?.+?)\)', '$3',
    '\.Returns\(\(\)\s=>\s', '.Returns(',
    'Mock.Of', 'Substitute.For',
    '\b(\w+)\.Verify\((\w+) => \w+\.([a-zA-Z0-9_]+)\((.*?)\)\);', '$1.Received().$3($4);'
    # Add more Find and Replace pairs here as needed.
)
 
# Convert the simple array to an array of custom objects for further processing.
$findReplaceItems = @()
 
for ($i = 0; $i -lt $findReplaceArray.Length; $i += 2) {
    $findReplaceItems += [PSCustomObject]@{
        Find    = $findReplaceArray[$i]
        Replace = $findReplaceArray[$i + 1]
    }
}
 
# Enumerate C# files and apply the replacements.
Get-ChildItem -Path $baseDirectory -Filter *.cs -Recurse | ForEach-Object {
 
    $fileName = $_.FullName
 
    # Skip generated files.
    if ($fileName -like "*.g.cs" -or $fileName -like "*AssemblyAttributes.cs") {
        return
    }
 
    $csFile = Get-Content -Path $fileName -Raw
 
    # Skip files that do not contain the text "Mock".
    if (-not $csFile.Contains("Mock")) {
        return
    }
 
    $csFileOriginalSize = $csFile.Length
 
    # Apply any required replacements.
    $findReplaceItems | ForEach-Object {
        $csFile = [Regex]::Replace($csFile, $_.Find, $_.Replace)
    }
 
    # If any replacments were made, update the file with the new contents.
    if ($csFile.Length -ne $csFileOriginalSize) {
        Write-Host "Replacing Moq in: $fileName"
        Set-Content -Path $fileName -Value $csFile
    }
}
 
Write-Host
Write-Host "Migration complete.`n"

The above script first attempts to read the directory that it should apply replacements within from a command-line argument. If an argument has not been specified, the directory that the script is currently located in will be used instead. This means that you can temporarily drop the script into your test project directory and remove it after running it for maximum convenience.

The script also includes a confirmation prompt to ensure that it is not started unintentionally without specific consent being granted by the user. The directory that the replacements are going to be made in is also displayed to remove any doubt.

The other key difference in the script compared to Chris’ program is that the Find and Replace patterns have been included within the script, instead of being located in an external config.txt file. This makes it convenient to simply add any additional Find and Replace patterns that you may require in the future into a singular script file. For example, I have added another pattern that can handle a Verify to Received conversion in cases where the number of times that the method invocation should have taken place has not been specified in the Verify method call.

Following this, the script logic is very similar to Chris’ program. The Get-ChildItem cmdlet is used to recursively iterate through all .cs files within the specified directory. Generated files are skipped and the content of user-defined files is read into memory.

If a file doesn’t contain the text “Mock” we skip it, otherwise, we run all of the Find and Replace operations on the string contents of the file.

Lastly, if the file size has changed, this indicates that one or more replacements were made, so we use the Set-Content cmdlet to write the new contents to the .cs file.

Running the script

To run the script, paste the above script code into your favourite text editor and save it as a PowerShell script file with a sensible name, such as ‘Migrate-MoqToNSubstitute.ps1’.

Next, open a PowerShell window and navigate to the directory containing the script file you just saved, then execute the following command.

./Migrate-MoqToNSubstitute.ps1 C:/<path_to_your_tests_project>

Note that you will need to change the script filename above to match your chosen name and modify the path argument to point to the location of the project containing your tests that currently use Moq.

Alternatively, copy the script file into the directory of the project containing your tests and then you can simply start the script using PowerShell without specifying any arguments, as follows.

./Migrate-MoqToNSubstitute.ps1

When running the script, the output displayed will be similar to the following.

Moq will be replaced with NSubstitute in the following directory.

C:\<path_to_your_tests_project>

Do you want to proceed? (y/n): y

Replacing Moq in: C:\<path_to_your_tests_project>\UnitTest1.cs
Replacing Moq in: C:\<path_to_your_tests_project>\UnitTest2.cs
Replacing Moq in: C:\<path_to_your_tests_project>\UnitTest3.cs

Migration complete.

Now, check the results and make any required manual changes to your tests where necessary.

Final whistle

While switching mocking frameworks may sound daunting, to begin with, substituting Moq with NSubstitute can become a relatively pain-free process with the help of the .NET community.

The Moq and NSubstitute libraries both have their nuances and although you may have grown to love Moq over the years I think you’ll find the NSubstitute syntax to be very intuitive, making the migration process smoother.

This article only skims the surface of NSubstitute’s capabilities, so I highly recommend diving into the official NSubstitute documentation to explore its full potential when you’re making the switch to ensure you are using NSubstitute effectively.

In closing, it’s important to remember that choosing a tool or library isn’t just about its features but is also about the ethos of the community and the maintainers behind it. I highly recommend that you always keep yourself informed and ensure that your choices align with the standards and principles you seek to uphold in your projects.


I hope you enjoyed this post! Comments are always welcome and I respond to all questions.

If you like my content and it helped you out, please check out the button below 🙂

Comments

Jason Bock

FWIW I’ve written a mocking framework that’s based on the source generator feature in C#. It’s called Rocks – feel free to check it out and let me know what you think.

https://github.com/JasonBock/Rocks/
https://nuget.org/packages/Rocks

April 16, 2024

Jonathan Crozier

Thanks for the comment, Jason.

Rocks looks like a very interesting project. If you’d like to write a guest blog post about it or raise awareness in some other manner just let me know!

April 16, 2024