But it works on my PC!

The random thoughts of Richard Fennell on technology and software development

My grok talk on Sharepoint testing with Typemock Isolator and Ivonna at DD Scotland

I had an enjoyable day at Developer Day Scotland in Glasgow yesterday; a big thank you to the organisers and speakers.

I did a short grok talk on ‘Testing Sharepoint using Typemock Isolator and Ivonna’, a few people asked me for more details. Well the session was based on a post I did a while ago. I have updated that post to tidy a couple of issue I found whilst preparing the session. If you need more details of the potential pitfalls in using these tools I suggest you also look at the MVC post I did a few days ago as this details the setup you need to get it going.

I will also be doing a longer session on the same subject at some user groups later in the summer

Testing access attributes on the Microsoft MVC framework

The MVC framework provides an excellent way to create a testable web site. In fact when you create a new MVC project you are given the option to create an associated test project that contains MSTEST unit tests for all the sample methods in the MVC Controller class; which you can add to as you go along.

Recently whilst working on an MVC project I noticed that the one area that this model does not let you test is that of access control. MVC uses attributes on Controller methods to limit who can access what e.g.

[Authorize]
public ActionResult ChangePassword()
{

ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

return View();
}

These attributes are used by the MVC routing engine to decide if a method can be called or not. The problem is these attributes are not honoured by the unit tests as they call the controller methods directly not via the routing engine.

This raised a concern for me, if I am setting access controller via attributes how can I make sure I set the right attribute on the right methods? This is important for regression testing. This issue has also been discussed on StackOverflow. An alternative is to not use this attribute security model, but to make the security checks within the controller methods programmatically. For some this might be the correct solution, but did not see right for me. If you are going to use a framework, try to use it as it was intended.

I therefore needed a way to honour the attributes whilst testing. One option would be to write code on the unit tests to check the attributes, but this was more reflection than I wanted to do at this time. So I thought of using Ivonna the Typemock add-in. This allows you to load a web page and process it via a mocked web delivery framework.

Now this plan seemed simple but as soon as I started I found it was more complex than expected. I hit some problem as I will detail as I go along. I must say thanks to Artem Smirnov who wrote Ivonna for all his help in sorting out the issues I was having, both in my usage/understanding and fixing bugs. I could not have written this post without his help.

Preparation- My Assumptions

So for this post lets assume the following

  • You create a new MVC project so you have the basic sample MVC web site.
  • You allow Visual Studio to also create a new Test Project for the MVC project
  • You have Typemock Isolator 5.3.0
  • You have Ivonna 1.2.7 what includes Artem’s “experimental support for MVC". (1.2.6.is OK for all bar the form submission example, see comments below)

GOTT’A 1: If you are writing tests using MSTEST with Ivonna you have to set the Test Project output directory to the bin directory of the MVC project (via Test Project’s properties, build tab)  e.g. ..\MvcApplication1\bin. This is needed so that Ivonna can find the page classes to load.

image .

Submitting a Form

 

The basic way MVC works is that forms get POSTed to the controller. So this was where I started, I think the comments in the code explain what is being done

[TestMethod, RunOnWeb(true)]
public void LogOn_FormContainingValidLoggedDetails_RedirectedToChangePasswordView()
{

// Arrange
// Fake out the membership service using Typemock, allow call through to the original
// class so that we don't need to fake out all the methods that will be called
var fakeMembershipService = Isolate.Fake.Instance<AccountMembershipService>(Members.CallOriginal);
// set that the Validate method will return true with the correct UID and password
Isolate.WhenCalled(() => fakeMembershipService.ValidateUser("testid", "goodpass")).WillReturn(true);

// Intercept the next call to the AccountController and slip in the faked service
Isolate.Swap.NextInstance<AccountMembershipService>().With(fakeMembershipService);

// Create the Ivonna test session
var session = new TestSession();

// Create the POST request, setting the AutoRedirect flag so that it does not
// actually do the redirect at the end of processing. We just check where it would
// redirect if we let it, this avoids an extra round trip
WebRequest request = new WebRequest(@"/Account/LogOn", "POST", null, null) { AutoRedirect = false };

// Fill in the form values with all the fields that would be sent on the Logon view submission
request.FormValues.Add("username", "testid");
request.FormValues.Add("password", "goodPass");
request.FormValues.Add("rememberMe", false.ToString());
request.FormValues.Add("returnUrl", @"/Account/ChangePassword");

// Act
// Process the request
WebResponse response = session.ProcessRequest(request);

// Assert
// Check that we have been redirected to the correct page
Assert.AreEqual(@"/Account/ChangePassword", response.RedirectLocation);
}

GOTT’A 2: If you are using the current shipping version of Ivonna (1.2.6 at the time of writing) this test will fail. There is a problem with form handling code so you need Artem’s “experimental support for MVC" release this addresses a problem with the form handling code (Updated 23 May 2009 - now shipped in 1.2.7). However as you will see from my comments below this problem might not as critical as I first thought.

This test is all well and good, but is it useful? All it proves is that Microsoft wrote their logon code correctly. They provide unit tests for this in the MVC source if you are interested. In my opinion if you are using a framework like you need to take it on trust and assume it’s core functions are tested prior to its publication (OK there will be bugs, but as a general point I think this holds true)

So I would say it is good you can do this test, but in practice I don’t think I would bother. If I want to test the functionality of methods in my controller class I should just use standard unit tests as in the MVC samples. I should test the functionality from separately from the security.

Checking for page differences between an anonymous user and an authenticated one

What I do want to test that a page is rendering correctly depending if I am logged in or not. The following two tests show how to do this with the home page of the default MVC sample.

I would draw your attention to how ‘clean’ the test is. Ivonna (and hence Typemock) is doing all the heavy lifting behind the scenes.

[TestMethod, RunOnWeb]
public void Home_IsNotLoggedOn_SeeLogonButton()
{
// Arrange
// create the Ivonna test session
var session = new TestSession();
// create the request for the page we want
WebRequest request = new WebRequest(@"/");
// set no user

// Act
WebResponse response = session.ProcessRequest(request);

// Assert
Assert.AreEqual(@"/", response.Url);
// we can check some html items on the form
Assert.IsTrue(response.BodyAsString.Contains("<h2>Welcome to ASP.NET MVC!</h2>"));
Assert.IsTrue(response.BodyAsString.Contains("[ <a href=\"/Account/LogOn\">Log On</a> ]"));
}


[TestMethod, RunOnWeb]
public void Home_IsLoggedOn_SeeLogOffButton()
{
// Arrange
// create the Ivonna test session
var session = new TestSession();
// create the request for the page we want
WebRequest request = new WebRequest(@"/");
// Pass in a user and the frame does the rest
request.User = new System.Security.Principal.GenericPrincipal(new System.Security.Principal.GenericIdentity("testid"), null);

// Act
WebResponse response = session.ProcessRequest(request);

// Assert
Assert.AreEqual(@"/", response.Url);
// we can check some html items on the form
Assert.IsTrue(response.BodyAsString.Contains("<h2>Welcome to ASP.NET MVC!</h2>"));
Assert.IsTrue(response.BodyAsString.Contains("Welcome <b>testid</b>!"));
Assert.IsTrue(response.BodyAsString.Contains("[ <a href=\"/Account/LogOff\">Log Off</a> ]"));
}

GOTT’A 3: When using classic ASP.NET Ivonna treats the returned page as an object and you have a collection of extension methods to help navigate the page checking control values etc. As MVC just returns HTML to the browser at this time you have to check for values by string matching in the response.BodyAsString (or you could use some xPath or Regular Expression)

TIP: As I am sure you will be looking at HTML from response.BodyAsString property in the debugger at some point, using the HTML visualizer in the Visual Studio Auto/Local Window is a great help. Select the HTML visualizer (as opposed to the default string one) by using the drop down selector in the window or tool trip debug prompt, and you can see the page as if in a browser for a quick manual visual test.

Checking who can see a page

Where I started with this project was wanting to test page access i.e. can user A get to Page B? We are now in a position to achieve this. The following two tests check that an authenticated user can reach a secured page, and that a non authenticated one is redirected to the logon page. Again note the clean easy to read syntax.

[TestMethod, RunOnWeb]
public void ShowChangePassword_NotAuthenticated_RedirectToLogonView()
{
// Arrange
var session = new TestSession();
var request = new WebRequest(@"/Account/ChangePassword");
// we set no value for the request.User property

// Act
var response = session.ProcessRequest(request);

// Assert
// redirected to the logon page
Assert.AreEqual(@"Account/LogOn?ReturnUrl=%2fAccount%2fChangePassword", response.Url);
}

[TestMethod, RunOnWeb]
public void ShowChangePassword_Authenticated_ShowChangePasswordView()
{
// Arrange
var session = new TestSession();
var request = new WebRequest(@"/Account/ChangePassword");
// just pass in a user, using the fact that Ivonna has a built-in authentication faking
request.User = new System.Security.Principal.GenericPrincipal(new System.Security.Principal.GenericIdentity("testid"), null);

// Act
var response = session.ProcessRequest(request);

// Assert
Assert.AreEqual(@"/Account/ChangePassword", response.Url);
// we can also check the page content, but probably don't need to in this case
Assert.IsTrue(response.BodyAsString.Contains("Use the form below to change your password."));
Assert.IsTrue(response.BodyAsString.Contains("Welcome <b>testid</b>!"));
Assert.IsTrue(response.BodyAsString.Contains("[ <a href=\"/Account/LogOff\">Log Off</a> ]"));

}

The above tests assume that the ChangePassword page is just protected with a simple [Authorize] attribute, so a user is authenticated or not. However, it is easy to modify the test so that it handles roles. If the same page was protected with the attribute [Authorize(Roles= "Staff")], the test simply becomes

[TestMethod, RunOnWeb]
public void ShowStaffOnlyChangePassword_Authenticated_ShowChangePasswordView()
{
// Arrange
var session = new TestSession();
var request = new WebRequest(@"/Account/ChangePassword");
// just pass in a user, using the fact that Ivonna has a built-in authentication faking
request.User = new System.Security.Principal.GenericPrincipal(new System.Security.Principal.GenericIdentity("testid") , new string[] {"Staff"});

// Act
var response = session.ProcessRequest(request);

// Assert
Assert.AreEqual(@"/Account/ChangePassword", response.Url);
// we can also check the page content, but probably don't need to in this case
Assert.IsTrue(response.BodyAsString.Contains("Use the form below to change your password."));
Assert.IsTrue(response.BodyAsString.Contains("Welcome <b>testid</b>!"));
Assert.IsTrue(response.BodyAsString.Contains("[ <a href=\"/Account/LogOff\">Log Off</a> ]"));

}

Should I use this way to test MVC?

If you are worried that developers are not applying (or are refactoring away) the security attributes on your MVC controllers this technique for testing is well worth a look.It provides a way of writing simple, readable tests for the MVC security model.

It is fair to say that these tests are not fast, the basic setup of a single test run takes about 30 seconds (but subsequent test in a batch are far faster) so you are not going to run them all the time in a TDD style. I think you should consider them as integration tests and run them as part of you continuous integration process. I think it is a more robust means of testing security than recorder based Web Tests. All thanks to Artem from producing such a useful Typemock add-in.

Licensing exception with Ivonna the Typemock add-in (and any other add-ins I suspect)

Like a good developer I have been trying to run Visual Studio with least privilege; with Windows 7 this seems to work well. My main account is not an administrator, but Windows prompts me for elevated rights when needed. I have been developing happily with Visual Studio and Typemock without any need for extra rights.

However, when I have been doing some testing using Ivonna, the Typemock add-in, I hit a problem. When I tried to create an Ivonna.Framework.TestSession() I got a Licensing.LicenseException: This copy has expired. Which it hadn’t as I have a fully licensed product.

I had got so use to not needing elevated privilege I did not consider it to be the problem; so I contacted Sm-Art and Typemock support. The answer was simply to run Visual Studio with administrator privileges (right click on the short cut). Once this is done the licensing exception goes away as Typemock has enough rights to look in the right bit of the registry to access the add-in license. I have suggested that if possible this requirement needs to be addressed.

The other alternative is to grant your non administrator account more rights in the registry. On a 64bit development box it seems you need to a Read-Write access to HKEY_LOCAL_MACHINE\SOFTWARE\TypeMock and HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\TypeMock

Team Build, Code Coverage and MVC

I have been working on some automated build and testing for a project based on the Microsoft MVC framework. The build was working fine, and test were being run, but I was not seeing any code coverage data in the build summary in Visual Studio for the builds done by the Team Build box. However if I ran the test suite locally on a development PC the coverage data was there. Looking on the Team Build drop location I could find the data.coverage file in the TestResults\<guid>\In\<build user> folder, but it was 84Kb in size, which I learnt means ‘contains no data’.

After a good deal of hunting I found a pointer to the answer the OZTFS forum. The problem is that as the MVC project is a web project, and it build a _PublishedWebsites folder and puts the assemblies into this. In effect the code coverage is just looking in the wrong place.

The fix is as follows:

  • Make sure you have suitable TestRunConfig1.testrunconfig file in your solution
  • Open this file in the VS IDE and make sure code coverage is enabled for the assemblies you want (and if your assemblies are signed that the re-sign key is set)
  • Open you tfsbuild.proj file for the automated team build and make sure you have a testing block similar to the block below, change the path to the RunConfigFile as required.

<!--  
TESTING Set this flag to enable/disable running tests as a post-compilation build step.
-->
<RunTest>true</RunTest>
<!--
CODE ANALYSIS Set this property to enable/disable running code analysis. Valid values for this property are
Default, Always and Never.
Default - Perform code analysis as per the individual project settings
Always - Always perform code analysis irrespective of project settings
Never - Never perform code analysis irrespective of project settings
-->
<RunCodeAnalysis>Default</RunCodeAnalysis>

<!--
CODE COVERAGE Set the test run configuration
-->
<RunConfigFile>$(SolutionRoot)\MyWebSolution\TestRunConfig1.testrunconfig</RunConfigFile>

  • If you test this locally you should get code coverage results, but if you run the build on a Team Build box the code coverage section in the test report will show "No coverage result"
  • Now the important bit – open the TestRunConfig1.testrunconfig file in Notepad and add an extra block to the <regular> code coverage section to additionally point to the assembly(s) in the _PublishedWebsites structure (you could also use the VS IDE on the build box to add the file if you wanted, but this will warn over an assembly being added twice). When complete the XML file should look similar to the one below
<?xml version="1.0" encoding="UTF-8"?>
<TestRunConfiguration name="TestRunConfig1" id="b6360bec-8278-4773-a931-f22bfab2c57f" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2006">
<Description>This is a default test run configuration for a local test run.</Description>
<CodeCoverage enabled="true" keyFile="MyWebsite\ProjectKey.snk">
<AspNet>
<AspNetCodeCoverageItem id="88655819-3261-43ac-b2d8-2d3aa1aabaef" name="MyWebsite" applicationRoot="/" url="http://localhost:0/" />
</AspNet>
<Regular>
<CodeCoverageItem binaryFile="C:\builds\MyWebsite\CIBuild\Binaries\Release\_PublishedWebsites\MyWebsite\bin\MyWebsite.dll" pdbFile="C:\builds\MyWebsite\CIBuild\Binaries\Release\_PublishedWebsites\MyWebsite\bin\MyWebsite.pdb" instrumentInPlace="true" />
</Regular>
</CodeCoverage>
<TestTypeSpecific>
<WebTestRunConfiguration testTypeId="4e7599fa-5ecb-43e9-a887-cd63cf72d207">
<Browser name="Internet Explorer 7.0">
<Headers>
<Header name="User-Agent" value="Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)" />
<Header name="Accept" value="*/*" />
<Header name="Accept-Language" value="{{$IEAcceptLanguage}}" />
<Header name="Accept-Encoding" value="GZIP" />
</Headers>
</Browser>
<Network Name="LAN" BandwidthInKbps="0" />
</WebTestRunConfiguration>
</TestTypeSpecific>
</TestRunConfiguration>

  • Once this is done you can run the tests locally or on the build machine and in both cases MSTest manages to find the assembly to test the code coverage on and reports the results.

Testing SharePoint Workflows using TypeMock Isolator (Part 3)

Updated 12 June 2009 - I have been having problems using this technique of Typemock with Sharepoint Workflows, the workflows keep unexpectedly idling as opposed to activating. If you suffer similar problem please check for later posts as to any solutions I find. 

Now I can test a basic workflow it soon becomes obvious that you could end up with many tests for a single workflow, as a workflow can have any number of criteria that could cause branching to occur. Maybe a sensible way to write the tests is using Fit/Fitness to provide the test cases in tabular form?

So to this end I have added a bit more code to my Typemock/Sharepoint test project (after installing the Fit libraries as detailed in my previous posts). I now have a single test that loads a set of test criteria from an HTML file (my previous posts discuss why I am using HTML files as opposed to the Fit Wiki).

   1: [TestMethod]
   2:      public void WorkFlow1SwitchOnTitle_DataViaFitnesse_Success()
   3:      {
   4:          fit.Runner.FolderRunner runner = new fit.Runner.FolderRunner(new fit.Runner.ConsoleReporter());
   5:          var errorCount = runner.Run(new string[] {
   6:                  "-i",@"WorkflowTestCases.htm", // the htm file that holds the test
   7:                  "-a",@"TestProject.dll",  //we have the fit facade in this assembly
   8:                  "-o",@"results"}); // the directory the results are dumped into as HTML
   9:          // fit can fail silently giving no failures as no test are run, so check for exceptions
  10:          Assert.AreEqual(false, Regex.IsMatch(runner.Results, "^0.+?0.+?0.+?0.+?$"), "No tests appear to have been run");
  11:          // look for expected errors
  12:          Assert.AreEqual(0, errorCount, runner.Results);
  13:          
  14:      }

I then have an HTML file that contains the test cases (remember to make sure that this file is deployed to the output directory)

import
TestProject
Workflow Fit Tests
Upload Document With Title Document Approved?
ABC True
XYZ False
abc True

 

   1: <HTML><HEAD>
   2:     <body>
   3: <table border="1" cellspacing="0">
   4: <tr><td>import</td>
   5: </tr>
   6: <tr><td>TestProject</td>
   7: </tr>
   8: </table>
   9:     
  10: <table border="1" cellspacing="0">
  11: <tr><td colspan="2">Workflow Fit Tests</td>
  12: </tr>
  13: <tr><td>Upload Document With Title </td>
  14: <td>Document Approved?</td>
  15: </tr>
  16: <tr><td>ABC</td>
  17: <td>True</td>
  18: </tr>
  19: <tr><td>XYZ</td>
  20: <td>False</td>
  21: </tr>
  22: <tr><td>abc</td>
  23: <td>True</td>
  24: </tr>
  25: </table>
  26:  
  27:     </body>
  28: </html>

 

Finally we need to create the facade class that wrapper the workflow function for Fit to call. In this sample I just popped the class in the test project for simplicity. Notice it is this facade class that contains all the Typemock bits, also that I make use of the helper class I created in my previous post to actually run the workflow.

   1: using System;
   2: using TypeMock.ArrangeActAssert;
   3: using Microsoft.SharePoint.Workflow;
   4:  
   5: namespace TestProject
   6: {
   7:     public class WorkflowFitTests : fit.ColumnFixture
   8:     {
   9:         public string UploadDocumentWithTitle;
  10:  
  11:         public bool DocumentApproved()
  12:         {
  13:  
  14:             var fakeProperties = Isolate.Fake.Instance<SPWorkflowActivationProperties>();
  15:             var fakeItem = fakeProperties.Item;
  16:             Isolate.WhenCalled(() => fakeItem.Title).WillReturn(this.UploadDocumentWithTitle);
  17:  
  18:             // Act
  19:             TypemockWorkflowTests.WorkflowRunner(typeof(SharePointWorkflow.Workflow1), fakeProperties);
  20:  
  21:             // Assert, if a document is approved must call the following two line
  22:             try
  23:             {
  24:                 Isolate.Verify.WasCalledWithExactArguments(() => fakeItem.Update());
  25:                 Isolate.Verify.WasCalledWithExactArguments(() => fakeItem["Approved"] = "True");
  26:                 return true; // we called all the updates expected
  27:             }
  28:             catch (TypeMock.VerifyException)
  29:             {
  30:                 // it did not call something expected, check if it was just the not approved path
  31:                 Isolate.Verify.WasNotCalled(() => fakeItem.Update());
  32:                 return false;
  33:             }
  34:  
  35:         }
  36:     }
  37: }

I am currently working on a sample of this testing technique, that does a bit more than a simple branch on if test, I will post a set of sample code when I am done.

Testing SharePoint Workflows using TypeMock Isolator (Part 2)

Updated 12 June 2009 - I have been having problems using this technique of Typemock with Sharepoint Workflows, the workflows keep unexpectedly idling as opposed to activating. If you suffer similar problem please check for later posts as to any solutions I find. 


After reading Gil’s blog on writing simpler tests I have done some tidying of the code from my previous post. In this version I have extracted the boiler plate code to run the workflow to a static helper method and modified my tests to incorporate Gil’s comments, they are certainly more readable.

   1: [TestMethod]
   2:       public void WorkFlowSwitchOnTitle_TitleStartsWithA_SetApprovelFieldAndUpdate()
   3:       {
   4:           // Arrange
   5:           var fakeProperties = Isolate.Fake.Instance<SPWorkflowActivationProperties>();
   6:           var fakeItem = fakeProperties.Item;
   7:           Isolate.WhenCalled(() => fakeItem.Title).WillReturn("ABC");
   8:  
   9:           // we actually don't need to create this field MOSS lets us attempt to write to
  10:           // it even if not declared, it would only need to be created if we check the value in the workflow
  11:           /*
  12:           var fakeField = fakeItem.Fields["Approved"];
  13:           fakeField.DefaultValue = false.ToString();
  14:           */
  15:  
  16:           // Act
  17:           WorkflowRunner(typeof(SharePointWorkflow.Workflow1),fakeProperties);
  18:           
  19:           // Assert
  20:           Isolate.Verify.WasCalledWithExactArguments(() => fakeItem.Update());
  21:           Isolate.Verify.WasCalledWithExactArguments(() => fakeItem["Approved"] = "True");
  22:  
  23:       }
  24:  
  25:       [TestMethod]
  26:       public void WorkFlowSwitchOnTitle_TitleStartsWithZ_DoNothing()
  27:       {
  28:           // Arrange
  29:           var fakeProperties = Isolate.Fake.Instance<SPWorkflowActivationProperties>();
  30:           var fakeItem = fakeProperties.Item;
  31:           Isolate.WhenCalled(() => fakeItem.Title).WillReturn("XYZ");
  32:  
  33:           // Act
  34:           WorkflowRunner(typeof(SharePointWorkflow.Workflow1),fakeProperties);
  35:  
  36:           // Assert
  37:           Isolate.Verify.WasNotCalled(() => fakeItem.Update());
  38:  
  39:       }
  40:  
  41:  
  42:  
  43:       /// <summary>
  44:       /// A helper method to run a workflow for a test
  45:       /// </summary>
  46:       /// <param name="wfType">The type of workflow to create</param>
  47:       /// <param name="fakeProperties">The fake properties used to create the workflow</param>
  48:       private static void WorkflowRunner(Type wfType, SPWorkflowActivationProperties fakeProperties)
  49:       {
  50:           using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
  51:           {
  52:               AutoResetEvent waitHandle = new AutoResetEvent(false);
  53:               workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
  54:               {
  55:                   // don't put asserts here as will be in the wrong thread
  56:                   waitHandle.Set();
  57:               };
  58:  
  59:               workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
  60:               {
  61:                   // don't put asserts here as will be in the wrong thread
  62:                   waitHandle.Set();
  63:               };
  64:  
  65:               // when this is called the constructor is called twice
  66:               // the first time is for validation for the workflow in this appdomain see http://odetocode.com/Blogs/scott/archive/2006/03/30/3192.aspx
  67:               // then the real construction is run, the problem is this double run means that the Isolate.Swap.NextInstance
  68:               // fails as it attaches to the first validation create, not the second real one
  69:               WorkflowInstance instance = workflowRuntime.CreateWorkflow(wfType);
  70:  
  71:               // SO for this reason we only get the swap after the first create has beend one
  72:               Isolate.Swap.NextInstance<SPWorkflowActivationProperties>().With(fakeProperties);
  73:  
  74:               // we then recreate the workflow again, this time it has already been validated so
  75:               // so the swap works
  76:               instance = workflowRuntime.CreateWorkflow(wfType);
  77:  
  78:               instance.Start();
  79:  
  80:               waitHandle.WaitOne();
  81:  
  82:               // the workflow is finished assert could go here, but will be done
  83:               // in the calling method
  84:  
  85:           }
  86:       
  87:       }

The workflow remains the same as in the previous post.