We previously talked about creating our own wrappers for dependencies that do not have mockable interfaces. Specifically we showed how to wrap Microsoft's Random class in a wrapper so we could mock the return and confirm that we were making the calls. There's a slightly different way we can do this, using something Microsoft has included for a few years, called Fakes (and Stubs, too).
Note: Unfortunately, Fakes don't seem to be available for .Net Core just yet so we'll have to cross our fingers and hope they add that support soon.
Using Fakes and Stubs is actually kind of the same process as writing our own wrappers, except we don't have to write our own wrappers. Since we've already talked about why we'd wrap Random, we won't go into it again here. Instead we'll jump right into the "hows" of getting things up and running.
The first thing we need to do is create a fake for whatever namespace the class we're going to fake or stub is in. In this case, Random is in System so we need to create a fake of that assembly. To do that, we expand References in our test project, right-click on System and choose "Add Fakes Assembly" from the context menu. After a moment, there will be a new folder in the project called Fakes with two files in it: mscorlib.fakes and System.fakes. After another moment you will see references to those new files in the References for the project: mscorlib.4.0.0.0.Fakes and System.4.0.0.0.Fakes (the version numbers may be different, depending on what's out there when you read this). You should also see a new reference to Microsoft.QualityTools.Testing.Fakes. Here's a picture of what you might see:
Now that we've added the right references we'll get started with writing our tests themselves. First we have to create a ShimsContext to run our tests in. ShimsContext implements IDisposable so we can do this by utilizing a using statement:
1: // create the ShimsContext in order to use Fakes later
2: using (ShimsContext.Create())
3: {
4: }
Within the ShimsContext we can use Fakes to specify the exact data we want to receive when we use static methods. The first method here specifies that when DateTime.Now is called by the code under test, it should be detoured to use “09/12/2017” (we want our shuffler to shuffle two decks on Tuesdays so we always want this specific test to behave as though it is running on a Tuesday):
System.Fakes.ShimDateTime.NowGet = () => new DateTime(2017, 09, 12);
That's all we need to do in order for this test to work. When DateTime.Now is called by our Code Under Test, the call will get intercepted and re-routed to use 09/12/2017 instead of today's actual date. This way we've guaranteed that the day will always appear to be a Tuesday when this test is run. Here's the full test:
1: [TestMethod]
2: public void ShuffleShouldShuffleTwoDecksOnTuesdays()
3: {
4: // create the ShimsContext in order to use Shims later
5: using (ShimsContext.Create())
6: {
7: // arrange
8: // intercept the call to System.DateTime.Now and always return a date
9: // that we're sure falls on a Tuesday (09/12/2017 is a Tuesday)
10: System.Fakes.ShimDateTime.NowGet = () => new DateTime(2017, 09, 12);
11: var shuffler = new Shuffler();
12:
13: // act
14: var cards = shuffler.Shuffle();
15:
16: // assert
17: Assert.AreEqual(104, cards.Count);
18: }
19: }
Stubs are a little bit easier to work with and don't require the ShimsContext in order to use them. We can create a stub pretty easily by specifying which methods we want to stub for the stubbed object. Perhaps an example would be best here.
1: var randomStub = new System.Fakes.StubRandom
2: {
3: NextInt32Int32 = (minValue, maxValue) =>
4: {
5: if (position == 1)
6: {
7: position = 53;
8: }
9: position--;
10: return position;
11: }
12: };
Once we've created the stub we need a way to provide it to the Code Under Test, which brings us all the way back around to dependency injection. We can either provide the dependency in the constructor or we can create a public setter to do so. In these tests we're using Setting Injection to show how it would be done since our ThoroughTest guide focuses more on Constructor Injection. Here's the test in full to get a better idea of what's happening:
1: [TestMethod]
2: public void ShuffleShouldShuffleDeck()
3: {
4: // arrange
5: var position = 53;
6: // setup the Random stub so that when Next is called with two
7: // integers (min value and max value) we always return a number
8: // we know
9:
10: // what we're going to do here is go backward through the unshuffled
11: // deck so the "shuffled" deck that we end up with will just be an
12: // inversion of the unshuffled deck (KS will be first and AH will be last)
13: var randomStub = new System.Fakes.StubRandom
14: {
15: NextInt32Int32 = (minValue, maxValue) =>
16: {
17: if (position == 1)
18: {
19: position = 53;
20: }
21: position--;
22: return position;
23: }
24: };
25:
26: var unshuffledDeck = new List<string>
27: {
28: "AH", "2H", "3H", "4H", "5H", "6H", "7H", "8H", "9H", "10H",
29: "JH", "QH", "KH", "AD", "2D", "3D", "4D", "5D", "6D", "7D",
30: "8D", "9D", "10D", "JD", "QD", "KD", "AC", "2C", "3C", "4C",
31: "5C", "6C", "7C", "8C", "9C", "10C", "JC", "QC", "KC", "AS",
32: "2S", "3S", "4S", "5S", "6S", "7S", "8S", "9S", "10S", "JS",
33: "QS", "KS"
34: };
35:
36: var shuffler = new Shuffler { Random = randomStub };
37:
38: // act
39: var cards = shuffler.Shuffle();
40:
41: // assert
42: var j = 51;
43: for (var i = 0; i < 51; i++)
44: {
45: Assert.AreEqual(unshuffledDeck[i], cards[j]);
46: j--;
47: }
48: }
You can see that we're creating an instance of the Shuffler class and then we're setting the Random property in that instance to the stub we created previously. It's important to note that we did not have to create a new ShimsContext in order for this to work properly. Fakes require the ShimsContext, but Stubs do not.
There's a lot we can do with Fakes and Stubs from Microsoft, but probably the best part is that this all comes out of the box with Visual Studio so we don't need to write our own wrappers or anything.
No comments:
Post a Comment