Mocking in .NET Core Unit Tests with Moq
Part 1: Unit Tests with Moq - 1
Replacing the actual dependency that would be used at production time, with a test-time-only version that enables easier isolation of the code we want to test.
Why Mock?
- 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
Note: when we use Moq framework we can use to create Dummies, Stubs and Mocks not for Fakes
So, we will use generic term “Mock” for Dummies, Stubs and Mocks
Moq
Pronounce as “Mock-you” or “Mock”
Opensource project
Mocking Method Calls
Create, Setup return and Use Mock Object
[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);
}
Instated of specifying an explicit value that Mock method to expect, we can use Mock use argument matching features
Using It.IsAny() method
[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);
Setup a mock method to return a specific value if the argument value is in within given set, using It.IsIn() method
mockValidator.Setup(x => x.IsValid(It.IsIn("x", "y", "z"))).Returns(true);Rather than specifying a list we can check within range by using It.IsInRange() method
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
|
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) |
Strict: exception will throw since no setup
Mock<IFrequentFlyerNumberValidator> mockValidator =
new Mock<IFrequentFlyerNumberValidator>(MockBehavior.Strict);
var sut = new CreditCardApplicationEvaluator(mockValidator.Object);
Loose: no exception will throw even no setup
Mock<IFrequentFlyerNumberValidator> mockValidator =
new Mock<IFrequentFlyerNumberValidator>();
var sut = new CreditCardApplicationEvaluator(mockValidator.Object);
Mocking Methods with “out” Parameters
setup method, rather-than chaining with .Return(true) we insteaed spesifiy the parameter created
[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
Configuring a mocked property
mockValidator.Setup(x => x.LicenseKey).Returns("EXPIRED");
mockValidator.Setup(x => x.LicenseKey).Returns(GetLicenseKeyExpiryString);string GetLicenseKeyExpiryString()
{
return "EXPIRED";
}
Auto-mocking Property Hierarchy
Moq is easy to mock properties even if there nestead in a hierachy. No need to mock all object in a hierachy, In background Moq has automatically created Mock for all object in hierachy.
[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);
}
Default Value Behavior for Mock property
For value type default value will be default value of the value type, but for the reference type default value will be null. Instead of null value for reference type we can change this behavior by specifying default value behavior in Moq. (this behavior only take effect when can actually be created for that type namely it has to be Interface, abstract or non-sealed class) when we add Moq behavior for object, instated of getting null it will create mock object.
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;
Tracking of changes to mock property value
We can configure properties to remember the changes made to them during the execution of the test
Method 1: we can setup the ValidationMode property remember all changes made to it during the execution of the test
[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);
}
Method 2: If we have lots of properties on the Mock object, we want to setup, rather than setup them individually, we can instead call SetupAllProperties method. Now all the properties in mock object have checking enabled. Call SetupAllProperties before any specific Setup to prevent override the specific setups.
[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 |
Implementing Behavior Verification Tests
When the thing we are trying to test doesn’t expose any useful state to allow us that it is working as expected.
Verifying a Method was called
[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>()));
}
Adding a Custom Error Message
mockValidator.Verify(x => x.IsValid(It.IsNotNull<string>()),
"Frequent flyer number passed should not be null");
Verifying a Method was not called
mockValidator.Verify(x => x.IsValid(It.IsAny<string>()), Times.Never);Verifying how many times a Method was called
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));
Verifying a Property Getter was called
mockValidator.VerifyGet(x => x.ServiceInformation.License.LicenseKey, Times.Once);Verifying a Property Setter was called
mockValidator.VerifySet(x => x.ValidationMode = It.IsAny<ValidationMode>(), Times.Once);Using Additional Mocking Techniques
Throwing Exceptions from Mock objects
Generic Exception:
mockValidator.Setup(x => x.IsValid(It.IsAny<string>()))
.Throws<Exception>();
Specific Exception
mockValidator.Setup(x => x.IsValid(It.IsAny<string>()))
.Throws(new Exception("Custom message"));
Raising Events from Mock Object
Raising manually:
[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);
}
Raising automatically:
[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);
}
Returning Different Results for Sequential Calls
SetupSequence – allows to do is chain multiple return methods for the setup
[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);
}
Mocking Members of Concrete Types
Mark the member as Virtual to access
Mocking Virtual Protected Members
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);
}



No comments:
Post a Comment