Microsoft has released a kind of “mocking” framework in Visual Studio 2012. In most cases when we are writing unit tests, we’re using frameworks like Moq, which helps us creating Mocks/Stubs. (For example: Mocking the data layer so that we can test the business layer.)
In this tutorial, you see how to use “Stub” and “Shim” of the “Fakes Framework”:
Stub | A implementation of a interface or abstract class which will be passed into the logic you want to test. |
Shim | Method interception at run-time. Allows you to overwrite the implementation of a specific type. (Even .NET types) |
This is the structure of my VS.NET solution.
Here you see the implemented business and data logic:
public class Bank { public static decimal TransferLimit = 10000; private IBankRepository _repo; public Bank(IBankRepository repo) { this._repo = repo; } public bool Transfer(int account1, int account2, decimal amount) { if (amount > Bank.TransferLimit) { var mailer = new SmtpClient(); mailer.Send("...@....", "...@....", "Transfer limit exceeded", string.Empty); return true; } else { this._repo.Withdraw(account1, amount); this._repo.Deposit(account2, amount); return true; } } public DateTime InactiveSince(int account) { return DateTime.Now; } }
public class BankRepository : IBankRepository { public bool Deposit(int account, decimal amount) { throw new NotImplementedException("todo"); } public bool Withdraw(int account, decimal amount) { throw new NotImplementedException("todo"); } }
Now in my Test project, I added fakes for the assemblies “Data” and “System”.
My test class contains three test methods:
Test 1
The first test method creates a Stub object for the repository and defines the return values for the methods “Deposit” and “Withdraw”.
[TestMethod] public void BankTransfer_StubExample_Interface() { /*** Arrange ***/ // Create stub for data access var stub = new Data.Fakes.StubIBankRepository() { // Return true DepositInt32Decimal = (account, amount) => true, // Return true WithdrawInt32Decimal = (account, amount) => true }; /*** Act ***/ // Call the business logic and pass the // data access stub var business = new Business.Bank(stub); var result = business.Transfer(1, 2, 75); // Assert Assert.IsTrue(result); }
The class “StubIBankRepository” is placed under the namespace “Data.Fakes”. “Data” is the namespace I’ve defined in my Data assembly.
That means:
Data interface | Data.IBankRepository |
Fake | Data.Fakes.StubIBankRepository |
In the “Act” section, the Stub object is passed to the Bank constructor and afterwards
the business logic “Transfer” is being executed.
This test tries to transfer 75 from account 1 to account 2.
The transfer limit is 10000 (see code of Bank class above). In this first test, the transfer is below the limit and the data layer will be called. When you scroll up you will see, that the concrete implementation of the IBankRepository will throw an NotImplementedException. But because we are working with a Stub object, the Stub will be called rather than the concrete implementation.
Test 2
The second test is more interesting. The code below tries to transfer 20000 from account 1 to account 2.
In that case, the business logic sends an “notification e-mail” to the bank. The Bank class uses directly the SmtpClient (no abstraction). In our unit test, we want to test the core logic without sending an e-mail.
Because the smtp client isn’t passed to the business logic, we have to intercept the object creation and fake the SmtpClient type at run-time. (Shim)
[TestMethod] public void BankTransfer_ShimExample_Smtp() { /*** Arrange ***/ // Create stub. (Stub won't be called in that test!) // We don't need to define method implemtations. var stub = new Data.Fakes.StubIBankRepository(); bool result = false; using (ShimsContext.Create()) { // "Replace" the real SmtpClient.Send implementation. // (Full path: System.Net.Mail.Fakes.ShimSmtpClient) ShimSmtpClient.Constructor = self => { var shim = new ShimSmtpClient(self); shim.SendStringStringStringString = (from, to, subject, body) => { }; }; /*** Act ***/ var business = new Business.Bank(stub); result = business.Transfer(1, 2, 20000); } /*** Assert ***/ Assert.IsTrue(result); }
The code above executes the “Transfer” business logic and fakes the SmtpClient so that no e-mail will be sent.
(For accessing the Shim class, you have to extend the System.fakes configuration.)
Test 3
Here the last example. This code shows how to fake the DateTime.Now object using Shim.
[TestMethod] public void BankTransfer_ShimExample_DateTime() { /*** Arrange ***/ var stub = new Data.Fakes.StubIBankRepository(); var date = new DateTime(2012, 01, 03); DateTime result = DateTime.MinValue; using (ShimsContext.Create()) { // Configure DateTime.Now getter. // Return always the same date! ShimDateTime.NowGet = () => date; /*** Act ***/ var business = new Business.Bank(stub); result = business.InactiveSince(1); } /*** Assert ***/ Assert.IsTrue(result == date); }
Happy unit testing! 😉
Download the Solution!