But it works on my PC!

The random thoughts of Richard Fennell on technology and software development

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: public partial class TestPage : System.Web.UI.Page
   2:  {
   3:     public TestPage()
   4:     {
   5:  
   6:        var fakeWeb = Isolate.Fake.Instance<SPWeb>();
   7:        Isolate.WhenCalled(() => SPControl.GetContextWeb(null)).WillReturn(fakeWeb);
   8:  
   9:        // return value for 1st call
  10:        Isolate.WhenCalled(() => fakeWeb.Lists["Centre Locations"].Items).WillReturnCollectionValuesOf(CreateCentreList());
  11:        // return value for all other calls
  12:        Isolate.WhenCalled(() => fakeWeb.Lists["Map Zoom Areas"].Items).WillReturnCollectionValuesOf(CreateZoomAreaList());
  13:     }
  14:  
  15:     private static List<SPListItem> CreateZoomAreaList()
  16:     {
  17:        var fakeZoomAreas = new List<SPListItem>();
  18:        fakeZoomAreas.Add(CreateZoomAreaSPListItem("London", 51.49275, -0.137722222, 2, 14));
  19:        return fakeZoomAreas;
  20:     }
  21:  
  22:     private static List<SPListItem> CreateCentreList()
  23:     {
  24:        var fakeSites = new List<SPListItem>();
  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));
  27:        return fakeSites;
  28:     }
  29:  
  30:     private static SPListItem CreateCentreSPListItem(string title, string address, string email, string url, string telephone, string fax, double lat, double lng)
  31:     {
  32:         var fakeItem = Isolate.Fake.Instance<SPListItem>();
  33:         Isolate.WhenCalled(() => fakeItem["Title"]).WillReturn(title);
  34:         Isolate.WhenCalled(() => fakeItem["Address"]).WillReturn(address);
  35:         Isolate.WhenCalled(() => fakeItem["Email Address"]).WillReturn(email);
  36:         Isolate.WhenCalled(() => fakeItem["Site URL"]).WillReturn(url);
  37:         Isolate.WhenCalled(() => fakeItem["Telephone"]).WillReturn(telephone);
  38:         Isolate.WhenCalled(() => fakeItem["Fax"]).WillReturn(fax);
  39:         Isolate.WhenCalled(() => fakeItem["Latitude"]).WillReturn(lat.ToString());
  40:         Isolate.WhenCalled(() => fakeItem["Longitude"]).WillReturn(lng.ToString());
  41:         return fakeItem;
  42:     }
  43:  
  44:     private static SPListItem CreateZoomAreaSPListItem(string areaName, double lat, double lng, double radius, int zoom)
  45:     {
  46:         var fakeItem = Isolate.Fake.Instance<SPListItem>();
  47:         Isolate.WhenCalled(() => fakeItem["Title"]).WillReturn(areaName);
  48:         Isolate.WhenCalled(() => fakeItem["Latitude"]).WillReturn(lat.ToString());
  49:         Isolate.WhenCalled(() => fakeItem["Longitude"]).WillReturn(lng.ToString());
  50:         Isolate.WhenCalled(() => fakeItem["Radius"]).WillReturn(radius.ToString());
  51:         Isolate.WhenCalled(() => fakeItem["Zoom"]).WillReturn(zoom.ToString());
  52:         return fakeItem;
  53:     }
  54:  
  55: }
  56:  

The problem is that if I place the following logic in my Webpart

   1: SPWeb web = SPControl.GetContextWeb(Context);
   2: Debug.WriteLine (web.Lists["Centre Locations"].Items.Count);
   3: Debug.WriteLine (web.Lists["Map Zoom Areas"].Items.Count);

I would expect this code to return

2
1

But I get

1
1

If I reverse two Isolate.WhenCalled lines in the constructor I get

2
2

So basically only the last Isolate.WhenCalled is being used, this is not what I expect from the Typemock documentation. .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.

A Workaround

I 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<GISPoint> LoadFixedMarkersFromSharepoint(SPWeb web, string listName)
   2: {
   3:     var points = new List<GISPoint>();
   4:  
   5:     foreach (SPListItem listItem in web.Lists[listName].Items)
   6:     {
   7:             points.Add(new GISPoint(
   8:                 listItem["title"], 
   9:                 listItem["address"], 
  10:                 listItem["email addess"], 
  11:                 listItem["site Url"], 
  12:                 listItem["telephone"], 
  13:                 listItem["fax"], 
  14:                 listItem["latitude"], 
  15:                 listItem["longitude"]));
  16:     }
  17:     return points;
  18: }
  19:  
  20: private List<ZoomArea> LoadZoomAreasFromSharepoint(SPWeb web, string listName)
  21: {
  22:          var points = new List<ZoomArea>();
  23:  
  24:          foreach (SPListItem listItem in web.Lists[listName].Items)
  25:          {
  26:            points.Add(new ZoomArea(
  27:                 listItem["title"],
  28:                 listItem["latitude"], 
  29:                 listItem["longitude"], 
  30:                 listItem["radius"], 
  31:                 listItem["zoom"]));
  32:          }
  33:          return points;
  34: }
  35:   

I 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<LocationMap>(Members.CallOriginal);
   2: Isolate.Swap.NextInstance<LocationMap>().With(controlWrapper);
   3:  
   4: Isolate.NonPublic.WhenCalled(controlWrapper, "LoadFixedMarkersFromSharepoint").WillReturn(CreateCentreListAsGISPoint());
   5: Isolate.NonPublic.WhenCalled(controlWrapper, "LoadZoomAreasFromSharepoint").WillReturn(CreateZoomAreaListAsZoomItems());
   6:   

My 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

I 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.

Running SPDisposeCheck as part of a 2010 CI Build

SPDisposeCheck is a great tool for SharePoint developers to make sure that they are disposing of resources correctly. The problem is it is a bit slow to run, a problem as this will mean developers will tend not to run it as often as they should. A good solution to the problem is to run it as part of the continuous integration process. There is are posts on how to do this via unit tests and as a MSBuild task, but I wanted to use a TFS 2010 style build. Turns out this is reasonably straight forward without the need to write a custom activity.

  • I created a build template based on the Default one.
  • After the compile and before the test step I added a InvokeProcess activity

image

  • I set the InvokeProcess properties as shown below, the edited settings are
    • Arguments: String.Format(“””{0}””””, outputDirectory) (remember you need the enclosing “ if your path could have spaces in it)
    • Filename: To the location of the SPDisposeCheck.exe file
    • Result: A previously created build variable of type Int32

image

image

  • This is done with a simple if check. If there are any errors found I write a build error message and set the TestStatus to failed. You might choose to set the build status to fail or any other flag you wish. The potential problem with my solution is that the TestStatus value could be reset by the tests that follow in the build process, but for a basic example of using the tool this is fine.

So it is easy to added a command line tool to the build. The key reason it is so easy is that SPDisposeCheck returns a number that we can use to see if the test passed or failed. hence we did not need to parse any text or XML results file. I wish more tools did this.

IDD Building a breakfast comment to a become process – now there is a leap

Gil at Typemock has been posting about some ideas we discussed over breakfast at the Typemock Partner conference a while ago, I have been a bit slow at commenting, so I though I better add to the conversation. Though Typemock is an excellent mocking framework, for me basic mocking is not its biggest win. All the ‘classic auto mocking’ of interfaces to speed TDD style working is great, but I can do that with any of the .NET mocking frameworks. All they do is mean I don’t have to write my own test stubs and mocks, so saving me time, which is good but not the keep win I was looking for.

For me there is another way to save much more time and that is to reduce my ‘build, deploy, use’ cycle. In the land of SharePoint this is a significant time saving, or at least has been for us. It has meant that I can replace the build, create WSP, deploy WSP, let SharePoint/IIS restart and then view a web part, with a build and view in ASP.NET page that uses Typemock to fake out all to SharePoint calls. This is what Gil has termed Isolation Driven Development (IDD) Now isn’t a three letter _DD name going a bit far, I am even not sure there enough in it for me to write a book!

That said this is a solid technique which can be applied to any complex environment where developers or testers need a means to mock out significant, costly, or just slow components to ease there daily work process, often enabling some manual testing process, thus making them more productive. If you read the TPS books it mentions a lot how workers should optimise their work space to reduce wasted time the spend moving between machines or roles, this is just such a move.

So if you want to use the technique for Sharepoint have a look at my post, I hope it will save you time whether on SP2007 or 2010, or maybe apply same technique to other technologies.

Setting the display value in databound combo boxes on Access 2010 web forms

When you drop a combo box on a Access 2010 web form and databind to a query or table with a value column and display column (e.g. the query select id, description from table1) you don’t get what you would expect. It shows just the value e.g. a set of integers in the combo.

This is not the case if you are using a standard Access client form. the wizard that gets run sort it all out for you, and if it does not, you just set the ‘Bound Column’ property to sort it out.

On web forms the fix is simple, but not obvious.

  1. Databind the combo as you normal do to the query/table.
  2. Go to the combo’s properties format tab
  3. Set the column count to 2
  4. Set the column widths to 0cm;2cm (basically hide the first column and show the second)

image

Once this is done it work fine

Notes from our TFS 2010 upgrade

Got time today to start our proper internal TFS 2010 rollout. This involves linking up the new TFS to our SharePoint 2010 farm and merging in the contents from 2008 TFS and 2010 RC servers.

All went fairly well, our SharePoint farm caused a few problems with its rather nice redirection feature. This  allows you to gradually move SharePoint content from an older server to a new one. A page is requested from the new server, if it is there it is used, if it is not the content on the old server is used. This caused a couple of problems during the install of TFS that content was not on the new SharePoint server so it redirected to the old one. However, the correct response was that the content was not there and TFS need to install or configure it. Once we realised this was going on there were no major issues with the TFS 2010 installation.

So we had a nice new TFS 2010 install. The next task was to move over content from the 2008 server. Our 2008 server was virtualised so I snaphot’d it, made sure we had a good SQL backup and did an in-place upgrade, and this is where I hit a problem. Our new TFS 2010 install and the 2008 were using the same central SQL server. Our new TFS 2010 install had created a TFS_Configuration DB, I had had the option to put in prefix such as TFS_2010_Configuration but decided I did not need it. This choice came back to bite me. When you do an in-place upgrade of 2008 to 2010 it rolls up all the old TFS DBs into the new structure, creating a new DB, with you guess it, the name TFS_Configuration. If you are doing a new install you can alter this DB name, but this is not the case for a upgrade, you have to use the standard name. So I was a bit stuck. The solution was to:

  1. take my new TFS 2010 server temporarily off line
  2. rename the TFS_Configuration DB
  3. do the in-place upgrade on the new 2008 server to 2010, so it creates as new TFS_Configuration DB
  4. detach the newly upgraded Team Project Collection on the TFS admin console
  5. take my upgraded TFS 2008 server off line
  6. rename its TFS_Configuration DB (if really confident you could delete it)
  7. rename the original TFS_Configuration DB back
  8. restart the main TFS 2010 server
  9. attached the upgraded TPC in the TFS admin console

Simple really !

Mocking Sharepoint for Testing

In my previous post I talked about using Isolator to mock Sharepoint to aid the speed of the development process. I find this a productive way of working, but it does not really help in the realm of automated testing. You need a way to programmatically explore a webpart, preferably outside of SharePoint to check its correctness.

You could use the methods in my previous post and some form of automated web test, but this does mean you need to spin up a web server of some descriptions (IIS, Cassini etc. and deploy to it) An alternative is look at the Typmock addin Ivonna. This a creates a fake web server to load you page and tools to explore it.

I will describe how to use this technique using the same example as my previous post.

Previously I had placed all the code to fake out SharePoint in the Page_Load event of the test harness page. As I am now trying to write an unit/integration test I think it better to move this into the test itself so I would delete code I placed in the Page_Load event other than any property settings on the actual webpart. I would actually refactor the lines creating the fake URL context and fake SPSite into some helper methods and then call them from my new test. I would then load the page in Ivonna and check it’s values.

I have tried to show this below, using a couple of techniques to show how to get to components in the page.

   1: [TestMethod, Isolated]
   2:      public void LoadWebPage_SpSimpleWebPart_3EntriesInList()
   3:      {
   4:          // Arrange
   5:          TestHelpers.CreateFakeSPSite();
   6:          TestHelpers.CreateFakeURL();
   7:  
   8:          TestSession session = new TestSession(); //Start each test with this
   9:          WebRequest request = new WebRequest("/SpSimpleTest.aspx"); //Create a WebRequest object
  10:          
  11:          // Act
  12:          WebResponse response = session.ProcessRequest(request); //Process the request
  13:  
  14:          // Assert
  15:          //Check the page loaded
  16:          Assert.IsNotNull(response.Page);
  17:          
  18:          // the the Ivonna extension method to find the control
  19:          var wp = response.Page.FindRecursive<DemoWebParts.SpSimpleWebPart>("wp1");
  20:          Assert.IsNotNull(wp);
  21:  
  22:          // so we have to use the following structure and dig knowing the format
  23:          // webpart/table/row/cell/control
  24:          var label = ((TableRow)wp.Controls[0].Controls[0]).Cells[1].Controls[0] as Label;
  25:          Assert.IsNotNull(label);
  26:          Assert.AreEqual("http://mockedsite.com",label.Text);
  27:  
  28:          var list = ((TableRow)wp.Controls[0].Controls[1]).Cells[1].Controls[0] as DropDownList;
  29:          Assert.IsNotNull(list);
  30:          Assert.AreEqual(3, list.Items.Count);
  31:      }

Now I have to say I had high hopes for this technique, but it has not been as useful as I would hope. I suspect that this is due to the rapid changing of the UI design of client’s webparts making these tests too brittle. We have found the ‘mark 1 eyeball’ more appropriate in many cases, as is so often true for UI testing.

However, I do see this as being a great option for smoke testing in long running projects with fairly stable designs.

Mocking Sharepoint for Design with Typemock Isolator

I have found the most productive use of Typemock Isolator with SharePoint is to use it to reduce the time of the F5 cycle (build/deploy/use). If you are using a VPC of some type to do your SharePoint development, as many do, this process can easily take a couple minutes, and these minutes add up.

In my experience webparts usually make fairly simple use of the underlying SharePoint site, by this I mean that they get some data from an SPList(s) or remote data source and render in some way. Or the reverse, they gather data that they drop to an SPLits(s) or remote data source.

So why not remove the requirement for SharePoint during development from the equation? Mock it out with Isolator and an ASP.NET test site.

image

Consider this scenario…

  • You have a part that lists people name and email address in a combo box
  • This data is stored in an SPList
  • The webpart must also list the URL of the site it is hosted on.

All fairly straight forward, but how to implement the wrapper around it?

  • The first we create an ASP.NET Web Application and an ASP.NET web page in the same solution as the WebParts class library.
  • In this web application reference the webpart project
  • Edit the .ASPX to reference the webpart assembly (line 2) and create an instance on the page (line 11-17). It is best to do this declaratively to avoid any question of the ASP.NET personalisation system.

 

   1: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SpSimpleTest.aspx.cs" Inherits="TestWebSite.SpSimpleTest" %>
   2: <%@ Register Assembly="DemoWebParts" Namespace="DemoWebParts" TagPrefix="wp" %>
   3: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   4: <html xmlns="http://www.w3.org/1999/xhtml">
   5: <head runat="server">
   6:     <title>Untitled Page</title>
   7: </head>
   8: <body>
   9:     <form id="form1" runat="server">
  10:     <div>
  11:         <asp:WebPartManager ID="WebPartManager1" runat="server">
  12:         </asp:WebPartManager>
  13:         <asp:WebPartZone ID="WebPartZone1" runat="server" >
  14:             <ZoneTemplate>
  15:                 <wp:SpSimpleWebPart ID="wp1" runat="server" />
  16:             </ZoneTemplate>
  17:         </asp:WebPartZone>
  18:     </div>
  19:     </form>
  20: </body>
  21: </html>

  • If you browse to this page you will see a null object exception as it loads the web part but it fails when it calls to SharePoint. This is because we have not mocked that yet. Seeing this error does rely on the fact you have a nice big global Try/Catch in the webparts Render() and CreateChildControls() methods. Note a technique I normally recommend but I think vital for this type of component else errors get swallowed by SharePoint

image

  • Add a reference to the SharePoint assemblies and Typemock Isolator (which of course you need a licensed copy of, or the 30 day demo) in the Test web application
  • In the Page_Load event you now need to add the code to do the faking, I think the comments below explain what is going on. (There is a good argument to refactor much of this into a TestHelper class so it is easy to reuse).
   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Web;
   5: using System.Web.UI;
   6: using System.Web.UI.WebControls;
   7: using TypeMock.ArrangeActAssert;
   8: using Microsoft.SharePoint;
   9:  
  10: namespace TestWebSite
  11: {
  12:     public partial class SpSimpleTestWithMockedSP : System.Web.UI.Page
  13:     {
  14:         protected void Page_Load(object sender, EventArgs e)
  15:         {
  16:  
  17:             // set the name of the list to read data from
  18:             wp1.DataList = "ListName";
  19:  
  20:             // set the fake return value for the currently running context
  21:             // we can us null as the current parameter as this is what this web page will return
  22:             Isolate.WhenCalled(() => Microsoft.SharePoint.WebControls.SPControl.GetContextSite(null).Url).WillReturn("http://mockedsite.com");
  23:  
  24:  
  25:             // create the mock SP Site we are using
  26:             SPSite fakeSite = Isolate.Fake.Instance<SPSite>();
  27:             Isolate.Swap.NextInstance<SPSite>().With(fakeSite);
  28:  
  29:             // create a fke collection to hold our test data
  30:             var itemCollection = new List<SPListItem>();
  31:             for (int i = 0; i < 3; i++)
  32:             {
  33:                 var fakeItem = Isolate.Fake.Instance<SPListItem>();
  34:                 itemCollection.Add(fakeItem);
  35:  
  36:                 Isolate.WhenCalled(() => fakeItem["Title"]).WillReturn(string.Format("Title {0}", i));
  37:                 Isolate.WhenCalled(() => fakeItem["Email Address"]).WillReturn(string.Format("email{0}@email.com", i));
  38:  
  39:  
  40:             }
  41:  
  42:             // set what is returned when a call is made for the list
  43:             Isolate.WhenCalled(() => fakeSite.RootWeb.Lists["ListName"].Items).WillReturnCollectionValuesOf(itemCollection);
  44:         }
  45:     }
  46: }
  • Once this code is entered you can browse to the page again and you should see a working webpart that thinks it is talking to a real SharePoint site.

image

So now you have a way to run you SharePoint dependant webpart outside of SharePoint.This means the F5 cycle is reduced to seconds as opposed to minutes, and debugging is loads easier.

What I find this particularly useful for is sorting CSS and JavaScript issues, where is loads of tiny edits to text files.

THIS DOES NOT MEAN YOU DON’T NEED TO TEST IN SHAREPOINT, but it does means you can avoid much of it. The SharePoint phase become far more a test/QA/UAT process as opposed a development one. This model as a developer productivity enabling one.

In my next post I will talk about Mocking Sharepoint for Test with Typemock Isolator

Post QCon thoughts

Interesting time at QCon yesterday,  shame I was only there one day, I do like the events that are not limited to a single vendor or technology. The multi presenter session I was involved in on Microsoft interoperability seemed to go well, there is talk of repeating at other events or podcasting. It is a nice format if you can get the sub-sessions linking nicely, like themed grok talks. 

Due to chatting to people (but that why you go really isn't it?), I only managed to get to one other session, but I was the one I wanted to see, Roy Osherove’s on using CThru to enable testing of monolithic frameworks such as Silverlight. It got a few things clearer in my mind over using CThu, a tool I have tried to use in the past but not had as much success as I hoped. So I think I will have another go at trying to build a SharePoint workflow testing framework, the problem has rested on the back burner too long. I think I just need to persist longer in digging to the eventing model to see why my workflows under test do not start. Roy’s comment that there is no short cut for this type of problem to avoid an archaeological excavation into the framework under test, I think is the key here.

Problem creating workitems on TFS2010 in the morning

I recently been working with a client who has been seeing strange problems when they try to create new workitems via a SharePoint portal site on a TFS2010 Beta2 installation. They appeared to have a fully working TFS2010 installation, but when they came in on a morning they found that even though they could login to the TFS created SharePoint team site they could not create a new workitems, they got an “Error 403 Access Forbidden”.

If they logged into SharePoint as a user with system administration rights it all worked fine. Now here is the strange bit, if they then logged in the user who got the 403 error it all worked fine, but when they came in the next morning it all happened again.

Turned out the issue was due to underling file access rights, once these were fixed all was OK. basically only the admin user had enough rights to populate a cache. Why this had occurred was still a bit of a mystery, but it is something you might see on any SharePoint installation. If you see a issue similar to this, the best option is to use Process Monitor to see if there are any file IO problems. This should point you in the right direction