In this article will compare two of most important mocking libraries for .Net (.Net Core and .Net Framework) NSubstitute and Moq.
What will we compare?
- Simplicity
- Readability
- Behaviours
The fight begins πͺ
Mock creation
Moq
var mock = new Mock<IRepository>();
NSubstitute
var mock = Substitute.For<IRepository>();
Moq uses a more representative notation. Anyone, just reading that line can know that we are creating a mock object.
Winner: Moq
Mocking properties
Moq
// Simple properties
mock.Setup(foo => foo.Users).Returns(userList);
// Hierarchy
mockUser.Setup(foo => foo.Address.Street).Returns(street);
NSubstitute
// Simple properties
mock.Users.Returns(userList);
// Hierarchy
mockUser.Address.Street.Returns(street);
NSubstitue has a simpler interface to set up the returns.
Winner: NSubstitue
Mocking methods
Moq
// Without parameters
mock.Setup(x => x.ActiveUsers()).Returns(userList);
// Matching by value
mock.Setup(x => x.SearchById(1)).Returns(user);
// Matching by any value of the parameter type
mock.Setup(x => x.SearchById(It.IsAny<int>())).Returns(user);
// Matching by custom logic
mock.Setup(x => x.SearchById(It.Is<int>(i => i < 10))).Returns(user);
NSubstitute
// Without parameters
mock.ActiveUsers().Returns(userList);
// Matching by value
mock.SearchById(1).Returns(user);
// Matching by any value of the parameter type
mock.SearchById(Arg.Any<int>()).Returns(user);
// Matching by custom logic
mock.SearchById(Arg.Is<int>(i => i < 10)).Returns(user);
Same here, NSubstitute has a simpler interface to set up the returns of the methods mocked.
Winner: NSubstitue
Matching arguments
Moq
// - It.IsAny<int>())
// - It.IsInRange(0, 10, Range.Inclusive)
// - It.IsIn(Enumerable.Range(1, 5))
// - It.IsNotIn(Enumerable.Range(1, 5))
// - It.IsNotNull<string>())
// - It.IsRegex("abc"))
// - It.Is<int>(i => i < 10))
// - mock.Setup(x => x.Search(IsLarge())) //< custom
NSubstitute
// - Arg.Any<int>())
// - Arg.Is<int>(i => i < 10))
Moq provide us a lot more built-in options for matching arguments
Winner: Moq
Capturing parameter
Moq
mock.Setup(x => x.Add(It.IsAny<IUserModel>())).Returns((IUserModel user) => user.Username == "Andres");
NSubstitute
mock.Add(Arg.Any<IUserModel>()).Returns(x => { return ((IUserModel)x[0]).Username == "Andres"; });
With Moq, it is easier to get the arguments values before returning the mocked value and process them as needed.
Winner: Moq
Multiple matching
Moq
mock.Setup(x => x.SearchById(It.IsAny<int>())).Returns((int i) => userList.Skip(1).Take(1).First());
mock.Setup(x => x.SearchById(2)).Returns((int i) => userList.First());
NSubstitute
mock.SearchById(Arg.Any<int>()).Returns(userList.Skip(1).Take(1).First());
mock.SearchById(2).Returns(userList.First());
Both of them allow us to add as many match expressions that we need. If there is a conflict they will take the last expression that matches.
Winner: Tie
Callabacks
Moq
mock.Setup(x => x.SearchById(1))
.Callback((int x) => parameterValue = x)
.Returns(userList.First());
// Methods without return
mock.Setup(x => x.Save()).Callback(() => name = "aa");
// before and after
mock.Setup(x => x.SearchById(2))
.Callback<int>(x => parameterValue = x)
.Returns(userList.First())
.Callback<int>(x => name = "andres");
NSubstitute
mock.SearchById(1)
.ReturnsForAnyArgs(x =>
{
parameterValue = (int)x[0];
return userList.First();
});
// Methods without return
mock.When(x => x.Save())
.Do(x => name = "aa");
// before and after
mock.SearchById(2)
.ReturnsForAnyArgs(x =>
{
parameterValue = (int)x[0];
return userList.First();
})
.AndDoes(x => name = "andres");
In this case Moq* provides a more readable experience adding a function called βCallbackβ on the chain call.
Winner: Moq
Multi-returns
Moq
mock.SetupSequence(x => x.Users)
.Returns(users1)
.Returns(users2)
.Returns(users3);
NSubstitute
mock.Search(Arg.Any<string>()).Returns(users1, users2, users3);
Here both libraries are very similar right?.
Winner: Tie
Throwing Exceptions
Moq
mock.Setup(x => x.Save()).Throws<Exception>();
mock.Setup(x => x.Save()).Throws(new Exception("msj"));
NSubstitute
mock.ActiveUsers().Returns(x => { throw new Exception(); });
mock.When(x => x.Save())
.Do(x => { throw new Exception("msj"); });
Moq is easier to recognize that the function will throw an exception.
Winner: Moq
Verify
Moq
// Setter
mock.VerifySet(x => x.Users = users);
// Getter
mock.VerifyGet(x => x.Users);
// Methods with matchting
mock.Verify(x => x.Search(It.IsAny<string>()));
mock.Verify(x => x.Search("aaa"), Times.Never());
// Occurrences
// - Times.Never
// - Times.Once
// - Times.AtLeastOnce
// - Times.AtMost(2)
// - Times.Exactly(2)
NSubstitute
// Setter
mock.Received().Users;
// Getter
mock.Received().Users = users;
// Methods with matchting
mock.Received().Search("aa");
mock.DidNotReceive().Search("aaa");
mock.Received(2).Search("bb");
// Occurrences
// Nop
NSubsitute has a simple way to verify that a method was called but Moq has more options to test how many times the method was executed.
Winner: Moq
Matching Generic Type Arguments
Moq
mock.Setup(m => m.AddUser(It.IsAny<It.IsSubtype<IUserModel>>())).Returns(true);
mock.Setup(m => m.AddUser(It.IsAny<UserModel2>())).Returns(false);
NSubstitute
mock.AddUser<IUserModel>(Arg.Any<IUserModel>()).Returns(true);
mock.AddUser<IUserModel>(Arg.Any<UserModel2>()).Returns(false);
Again, both provide an easy way to mock Generic Methods
Winner: tie
Well, how is it going? Who is winning?
Letβs see the current score
Syntax | Moq | NSubstitute |
---|---|---|
Mock creation | β | |
Mocking properties | β | |
Mocking methods | β | |
Matching arguments | β | |
Multiple matching | tie | tie |
Callabacks | β | |
Multi-returns | tie | tie |
Throwing Exceptions | β | |
Verify | β | |
Matching Generic Type Arguments | tie | tie |
Moq is the winner!! π π
I really prefer Moq and I try to use it always but both are excellent options to use. Us, as developers, we should be able to work with both.
You can download the examples from my Github
https://github.com/andreslozadamosto/net-thoughts/tree/master/libraries/testing/MoqVsNSubstitute/MockingLibrariesExamples
Are you with me?
Do you prefer NSubstitue?
I forget to compare any behaviour?
Share your comments π€π
Discussion (0)