Mocking in .NET Core Unit Tests with Moq
- Improved test execution speed
- Slow algorithms
- External resources: DB, Web service, etc.
- Support parallel development streams
- Real object not yet developed
- Another team working on the depended object
- External contractor
- Improve test reliability
- Reduce development/testing costs
- External company bills per usage
- Interfacing with mainframe
- Developer effort (complexity)
- Test when non-deterministic dependency
- Can set return specific value return from dependency every time without failing
“Unit – it’s a situational thing – the team decides what makes sense to be a unit for the purposes of their understanding of the system and its testing” (Martin Fowler)
Test Doubles
“Test Double is a generic term for any case where you replace a production object for testing purposes.” (Martin Fowler)
- Fake
- Working implementation of the dependencies
- However, Not suitable for production eg: EF Core in-memory provider
- Dummies
- Can be created and passed around system but they never actually used or accessed by the code been executed as part of our tests.
- Stubs
- Can create stubs and pass them as dependencies to code we are testing, but we can setup these stubs to provide and answers to calls made from the classes that we are testing in our test code.
- Mock
- Can create mocks as dependencies to code we are testing, when we use mock, can expect calls made that mock object from the class we are testing
Moq
Mocking Method Calls
[Fact]
public void DeclineLowIncomeApplications()
{
Mock<IFrequentFlyerNumberValidator> mockValidator =
new Mock<IFrequentFlyerNumberValidator>();
mockValidator.Setup(x => x.IsValid("x")).Returns(true);
var sut = new CreditCardApplicationEvaluator(mockValidator.Object);
var application = new CreditCardApplication
{
GrossAnnualIncome = 19_999,
Age = 42,
FrequentFlyerNumber = "x"
};
CreditCardApplicationDecision decision = sut.Evaluate(application);
Assert.Equal(CreditCardApplicationDecision.AutoDeclined, decision);
}
[Fact]
public void DeclineLowIncomeApplications()
{
Mock<IFrequentFlyerNumberValidator> mockValidator =
new Mock<IFrequentFlyerNumberValidator>();
mockValidator.Setup(x => x.IsValid(It.IsAny<string>())).Returns(true);
var sut = new CreditCardApplicationEvaluator(mockValidator.Object);
var application = new CreditCardApplication
{
GrossAnnualIncome = 19_999,
Age = 42
};
CreditCardApplicationDecision decision = sut.Evaluate(application);
Assert.Equal(CreditCardApplicationDecision.AutoDeclined, decision);
}
mockValidator.Setup(x => x.IsValid(It.Is<string>(number => number.StartsWith('x'))))
.Returns(true);
mockValidator.Setup(x => x.IsValid(It.IsIn("x", "y", "z"))).Returns(true);mockValidator.Setup(x => x.IsValid(It.IsInRange("b", "z", Range.Inclusive)))
.Returns(true);
mockValidator.Setup(x => x.IsValid(It.IsRegex("[a-z]",
System.Text.RegularExpressions.RegexOptions.None)))
.Returns(true);
|
MockBehavior.Strict |
Throw an exception if a mocked method is called but has not been
setup |
|
MockBehavior.Loose |
Never throw exceptions, even if a mocked method is called but has not
been setup Returns default values for value types, null for reference types,
empty array/enumerable/ |
|
MockBehavior.Default |
Default behavior if none specified (MockBehavior.Loose) |
Mock<IFrequentFlyerNumberValidator> mockValidator =
new Mock<IFrequentFlyerNumberValidator>(MockBehavior.Strict);
var sut = new CreditCardApplicationEvaluator(mockValidator.Object);
Mock<IFrequentFlyerNumberValidator> mockValidator =
new Mock<IFrequentFlyerNumberValidator>();
var sut = new CreditCardApplicationEvaluator(mockValidator.Object);
Mocking Methods with “out” Parameters
[Fact]
public void DeclineLowIncomeApplicationsOutDemo()
{
Mock<IFrequentFlyerNumberValidator> mockValidator =
new Mock<IFrequentFlyerNumberValidator>();
bool isValid = true;
mockValidator.Setup(x => x.IsValid(It.IsAny<string>(), out isValid));
Mocking Properties
mockValidator.Setup(x => x.LicenseKey).Returns("EXPIRED");
mockValidator.Setup(x => x.LicenseKey).Returns(GetLicenseKeyExpiryString);string GetLicenseKeyExpiryString()
{
return "EXPIRED";
}
[Fact]
public void ReferWhenLicenseKeyExpired()
{
var mockValidator = new Mock<IFrequentFlyerNumberValidator>();
mockValidator.Setup(x => x.IsValid(It.IsAny<string>())).Returns(true);
//var mockLicenseData = new Mock<ILicenseData>();
//mockLicenseData.Setup(x => x.LicenseKey).Returns("EXPIRED");
//var mockServiceInfo = new Mock<IServiceInformation>();
//mockServiceInfo.Setup(x => x.License).Returns(mockLicenseData.Object);
//mockValidator.Setup(x => x.ServiceInformation).Returns(mockServiceInfo.Object);
mockValidator.Setup(x => x.ServiceInformation.License.LicenseKey)
.Returns("EXPIRED");
var sut = new CreditCardApplicationEvaluator(mockValidator.Object);
var application = new CreditCardApplication { Age = 42 };
CreditCardApplicationDecision decision = sut.Evaluate(application);
Assert.Equal(CreditCardApplicationDecision.ReferredToHuman, decision);
}
Mock<IFrequentFlyerNumberValidator> mockValidator =
new Mock<IFrequentFlyerNumberValidator>();
mockValidator.Setup(x => x.IsValid(It.IsAny<string>())).Returns(true);
//mockValidator.Setup(x => x.ServiceInformation.License.LicenseKey);
mockValidator.DefaultValue = DefaultValue.Mock;
[Fact]
public void UseDetailedLookupForOlderApplications()
{
var mockValidator = new Mock<IFrequentFlyerNumberValidator>();
mockValidator.Setup(x => x.ServiceInformation.License.LicenseKey).Returns("OK");
mockValidator.SetupProperty(x => x.ValidationMode);
var sut = new CreditCardApplicationEvaluator(mockValidator.Object);
var application = new CreditCardApplication { Age = 30 };
CreditCardApplicationDecision decision = sut.Evaluate(application);
Assert.Equal(ValidationMode.Detailed, mockValidator.Object.ValidationMode);
}
[Fact]
public void UseDetailedLookupForOlderApplications()
{
var mockValidator = new Mock<IFrequentFlyerNumberValidator>();
mockValidator.SetupAllProperties();
mockValidator.Setup(x => x.ServiceInformation.License.LicenseKey).Returns("OK");
//mockValidator.SetupProperty(x => x.ValidationMode);
var sut = new CreditCardApplicationEvaluator(mockValidator.Object);
var application = new CreditCardApplication { Age = 30 };
CreditCardApplicationDecision decision = sut.Evaluate(application);
Assert.Equal(ValidationMode.Detailed, mockValidator.Object.ValidationMode);
}
Behavior Testing and State Based Testing
![]() |
| State based testing |
![]() |
| Behavior based testing |
[Fact]
public void ShouldValidateFrequentFlyerNumberForLowIncomeApplications()
{
var mockValidator = new Mock<IFrequentFlyerNumberValidator>();
mockValidator.Setup(x => x.ServiceInformation.License.LicenseKey).Returns("OK");
var sut = new CreditCardApplicationEvaluator(mockValidator.Object);
var application = new CreditCardApplication { FrequentFlyerNumber = "q" };
sut.Evaluate(application);
mockValidator.Verify(x => x.IsValid(It.IsAny<string>()));
}
mockValidator.Verify(x => x.IsValid(It.IsNotNull<string>()),
"Frequent flyer number passed should not be null");
mockValidator.Verify(x => x.IsValid(It.IsAny<string>()), Times.Never); mockValidator.Verify(x => x.IsValid(It.IsAny<string>()), Times.Once);
mockValidator.Verify(x => x.IsValid(It.IsAny<string>()), Times.Exactly(1));
mockValidator.Verify(x => x.IsValid(It.IsAny<string>()), Times.AtLeastOnce());
mockValidator.Verify(x => x.IsValid(It.IsAny<string>()), Times.AtMostOnce());
mockValidator.Verify(x => x.IsValid(It.IsAny<string>()), Times.AtMost(1));
mockValidator.VerifyGet(x => x.ServiceInformation.License.LicenseKey, Times.Once); mockValidator.VerifySet(x => x.ValidationMode = It.IsAny<ValidationMode>(), Times.Once);Using Additional Mocking Techniques
mockValidator.Setup(x => x.IsValid(It.IsAny<string>()))
.Throws<Exception>();
mockValidator.Setup(x => x.IsValid(It.IsAny<string>()))
.Throws(new Exception("Custom message"));
[Fact]
public void IncrementLookupCount()
{
Mock<IFrequentFlyerNumberValidator> mockValidator = new Mock<IFrequentFlyerNumberValidator>();
mockValidator.Setup(x => x.ServiceInformation.License.LicenseKey).Returns("OK");
mockValidator.Setup(x => x.IsValid(It.IsAny<string>())).Returns(true);
var sut = new CreditCardApplicationEvaluator(mockValidator.Object);
var application = new CreditCardApplication { FrequentFlyerNumber = "x", Age = 25 };
//sut.Evaluate(application);
mockValidator.Raise(x => x.ValidatorLookupPerformed += null, EventArgs.Empty);
Assert.Equal(1, sut.ValidatorLookupCount);
}
[Fact]
public void IncrementLookupCount()
{
Mock<IFrequentFlyerNumberValidator> mockValidator = new Mock<IFrequentFlyerNumberValidator>();
mockValidator.Setup(x => x.ServiceInformation.License.LicenseKey).Returns("OK");
mockValidator.Setup(x => x.IsValid(It.IsAny<string>()))
.Returns(true)
.Raises(x => x.ValidatorLookupPerformed += null, EventArgs.Empty);
var sut = new CreditCardApplicationEvaluator(mockValidator.Object);
var application = new CreditCardApplication { FrequentFlyerNumber = "x", Age = 25 };
sut.Evaluate(application);
mockValidator.Raise(x => x.ValidatorLookupPerformed += null, EventArgs.Empty);
Assert.Equal(1, sut.ValidatorLookupCount);
}
[Fact]
public void ReferInvalidFrequentFlyerApplications_Sequence()
{
Mock<IFrequentFlyerNumberValidator> mockValidator = new Mock<IFrequentFlyerNumberValidator>();
mockValidator.Setup(x => x.ServiceInformation.License.LicenseKey).Returns("OK");
mockValidator.SetupSequence(x => x.IsValid(It.IsAny<string>()))
.Returns(false)
.Returns(true);
var sut = new CreditCardApplicationEvaluator(mockValidator.Object);
var application = new CreditCardApplication { Age = 25 };
CreditCardApplicationDecision firstDecision = sut.Evaluate(application);
Assert.Equal(CreditCardApplicationDecision.ReferredToHuman, firstDecision);
CreditCardApplicationDecision secondDecision = sut.Evaluate(application);
Assert.Equal(CreditCardApplicationDecision.AutoDeclined, secondDecision);
}
mockFraudLookup.Protected()
.Setup<bool>("CheckApplication", ItExpr.IsAny<CreditCardApplication>())
.Returns(true);
Improving Mock Setup Readability with LINQ to Mocks
[Fact]
public void LinqToMocks()
{
//Mock<IFrequentFlyerNumberValidator> mockValidator = new Mock<IFrequentFlyerNumberValidator>();
//mockValidator.Setup(x => x.ServiceInformation.License.LicenseKey).Returns("OK");
//mockValidator.Setup(x => x.IsValid(It.IsAny<string>())).Returns(true);
IFrequentFlyerNumberValidator mockValidator
= Mock.Of<IFrequentFlyerNumberValidator>
(
validator =>
validator.ServiceInformation.License.LicenseKey == "OK" &&
validator.IsValid(It.IsAny<string>()) == true
);
var sut = new CreditCardApplicationEvaluator(mockValidator);
var application = new CreditCardApplication { Age = 25 };
CreditCardApplicationDecision decision = sut.Evaluate(application);
Assert.Equal(CreditCardApplicationDecision.AutoDeclined, decision);
}


















