Tuesday, May 12, 2020

Unit Tests with Moq - 2

Mocking in .NET Core Unit Tests with Moq


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);
        }

If we want to setup the return value of a mock method based on the result of some predicate we can use this using It.Is method with predicate
            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);

Also, we can match based on regular expression by using It.IsRegex() method
            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);


Note: Use strict mocks only when absolutely necessary, prefer loose mocks at all other times.


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");

Getting a rerun value from a method:
            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);
        }


Share This Post →

No comments:

Post a Comment

Powered By Blogger |   Design By Seo Blogger Templates Published.. Blogger Templates
DMCA.com