Problem faking multiple SPLists with Typemock Isolator in a single test
I have found a problem with repeated calls to indexed SharePoint Lists with Typemock Isolator 6.0.3. This what I am trying to do…
The Problem
I am using Typemock Isolator to allow me to develop a SharePoint Webpart outside of the SharePoint environment (there is a video about this on the Typemock site). My SharePoint Webpart uses data drawn from a pair of SharePoint lists to draw a map using Google maps API; so in my test harness web site page I have the following code in the constructor that fakes out the two SPLists and populates them with test content.
1 1: public partial class TestPage : System.Web.UI.Page
2: {
1 3: public TestPage()
4: {
1 5:
6: var fakeWeb = Isolate.Fake.Instance
1 7: Isolate.WhenCalled(() => SPControl.GetContextWeb(null)).WillReturn(fakeWeb);
8:
1 9: // return value for 1st call
10: Isolate.WhenCalled(() => fakeWeb.Lists["Centre Locations"].Items).WillReturnCollectionValuesOf(CreateCentreList());
1 11: // return value for all other calls
12: Isolate.WhenCalled(() => fakeWeb.Lists["Map Zoom Areas"].Items).WillReturnCollectionValuesOf(CreateZoomAreaList());
1 13: }
14:
1 15: private static List<SPListItem> CreateZoomAreaList()
16: {
1 17: var fakeZoomAreas = new List<SPListItem>();
18: fakeZoomAreas.Add(CreateZoomAreaSPListItem("London", 51.49275, -0.137722222, 2, 14));
1 19: return fakeZoomAreas;
20: }
1 21:
22: private static List
1 23: {
24: var fakeSites = new List
1 25: fakeSites.Add(CreateCentreSPListItem("Aberdeen ", "1 The Road, Aberdeen ", "Aberdeen@test.com", "www.Aberdeen.test.com", "1111", "2222", 57.13994444, -2.113333333));
26: fakeSites.Add(CreateCentreSPListItem("Altrincham ", "1 The Road, Altrincham ", "Altrincham@test.com", "www.Altrincham.test.com", "3333", "4444", 53.38977778, -2.349916667));
1 27: return fakeSites;
28: }
1 29:
30: private static SPListItem CreateCentreSPListItem(string title, string address, string email, string url, string telephone, string fax, double lat, double lng)
1 31: {
32: var fakeItem = Isolate.Fake.Instance
1 33: Isolate.WhenCalled(() => fakeItem\["Title"\]).WillReturn(title);
34: Isolate.WhenCalled(() => fakeItem["Address"]).WillReturn(address);
1 35: Isolate.WhenCalled(() => fakeItem\["Email Address"\]).WillReturn(email);
36: Isolate.WhenCalled(() => fakeItem["Site URL"]).WillReturn(url);
1 37: Isolate.WhenCalled(() => fakeItem\["Telephone"\]).WillReturn(telephone);
38: Isolate.WhenCalled(() => fakeItem["Fax"]).WillReturn(fax);
1 39: Isolate.WhenCalled(() => fakeItem\["Latitude"\]).WillReturn(lat.ToString());
40: Isolate.WhenCalled(() => fakeItem["Longitude"]).WillReturn(lng.ToString());
1 41: return fakeItem;
42: }
1 43:
44: private static SPListItem CreateZoomAreaSPListItem(string areaName, double lat, double lng, double radius, int zoom)
1 45: {
46: var fakeItem = Isolate.Fake.Instance
1 47: Isolate.WhenCalled(() => fakeItem\["Title"\]).WillReturn(areaName);
48: Isolate.WhenCalled(() => fakeItem["Latitude"]).WillReturn(lat.ToString());
1 49: Isolate.WhenCalled(() => fakeItem\["Longitude"\]).WillReturn(lng.ToString());
50: Isolate.WhenCalled(() => fakeItem["Radius"]).WillReturn(radius.ToString());
1 51: Isolate.WhenCalled(() => fakeItem\["Zoom"\]).WillReturn(zoom.ToString());
52: return fakeItem;
1 53: }
54:
1 55: }
56:
1
2The problem is that if I place the following logic in my Webpart
1: SPWeb web = SPControl.GetContextWeb(Context);
1 2: Debug.WriteLine (web.Lists\["Centre Locations"\].Items.Count);
3: Debug.WriteLine (web.Lists["Map Zoom Areas"].Items.Count);
1
2I would expect this code to return
3
4> 2
5> 1
6
7But I get
8
9> 1
10> 1
11
12If I reverse two Isolate.WhenCalled lines in the constructor I get
13
14> 2
15> 2
16
17So basically only the last Isolate.WhenCalled is being used, this is not what I expect from the [Typemock documentation](http://www.typemock.com/Docs/UserGuide/newGuide/Documentation/SettingBehaviorAAA.html). .This states that, worst case, the first Isolate.WhenCalled should be used for the first call and the second for all subsequent calls, and actually the index string should be used to differentiate anyway. This is obviously not working. I actually also tried using null in place of the both the index strings and got the same result.
18
19**A Workaround**
20
21I have managed to workaround this problem with a refactor of my code. In my web part I used to moved all the SPList logic into a pair of methods
1: private List
1 2: {
3: var points = new List
1 4:
5: foreach (SPListItem listItem in web.Lists[listName].Items)
1 6: {
7: points.Add(new GISPoint(
1 8: listItem\["title"\],
9: listItem["address"],
1 10: listItem\["email addess"\],
11: listItem["site Url"],
1 12: listItem\["telephone"\],
13: listItem["fax"],
1 14: listItem\["latitude"\],
15: listItem["longitude"]));
1 16: }
17: return points;
1 18: }
19:
1 20: private List<ZoomArea> LoadZoomAreasFromSharepoint(SPWeb web, string listName)
21: {
1 22: var points = new List<ZoomArea>();
23:
1 24: foreach (SPListItem listItem in web.Lists\[listName\].Items)
25: {
1 26: points.Add(new ZoomArea(
27: listItem["title"],
1 28: listItem\["latitude"\],
29: listItem["longitude"],
1 30: listItem\["radius"\],
31: listItem["zoom"]));
1 32: }
33: return points;
1 34: }
35:
1
2I then used Isolator to intercept the calls to these methods, this can be done by using the Members.CallOriginal flag to wrapper the actual class and intercept the calls to the private methods. Note that I am using different helper methods to create the list of my own data objects as opposed to List<SPListItems>
1: var controlWrapper = Isolate.Fake.Instance
1 2: Isolate.Swap.NextInstance<LocationMap>().With(controlWrapper);
3:
1 4: Isolate.NonPublic.WhenCalled(controlWrapper, "LoadFixedMarkersFromSharepoint").WillReturn(CreateCentreListAsGISPoint());
5: Isolate.NonPublic.WhenCalled(controlWrapper, "LoadZoomAreasFromSharepoint").WillReturn(CreateZoomAreaListAsZoomItems());
1 6:
2```
3
4My workaround, in my opinion, is a weaker test as I am not testing my conversion of SPListItems to my internal data types, but at least it works
5
6I have had to go down this route due to a bug in Typemock Isolator (which has been logged and recreated by Typemock, so I am sure we can expect a fix soon). However it does show how powerful Isolator can be when you have restrictions in the changes you can make to a code base.Wrapping a class with Isolator can upon up a whole range of options.