次の方法で共有


Unit Test a project having external dependency(WCF Proxy) using Fakes & Visual Studio 11 Beta

In this post I’ll explain the steps to generate unit tests for a project which calls a WCF service using Fakes. Microsoft Fakes is an isolation framework for creating delegate-based test stubs and shims in .NET Framework applications. The Fakes framework can be used to shim any .NET method, including non-virtual and static methods in sealed types.

The Fakes framework helps developers create, maintain, and inject dummy implementations in their unit tests. The Fakes framework generates and maintains stub types and shim types for a system under test.

I had previously discussed about creating Unit Tests using Pex, Moles and Visual Studio 2010. The projects inside the sample solution are

  1. DemoService: This project is a WCF Service.
  2. DemoLibrary: This project is a Class library and service reference to DemoService has been added. Unit tests will be generated for this project.
  3. ConsoleApp: This project is a Console application.
  4. DemoLibrary.Tests: This is a Test project and contains unit tests for DemoLibrary.

The solution structure is displayed below

image

DemoLibrary calls DemoService though proxy as displayed in the Layer diagram

image

I’ll now discuss in brief the code snippets of each project

WCF Service(DemoService): This service provides only a single operation

 [ServiceContract]
 public interface IDemoService
 {
     [OperationContract]
     string Search(string criteria);
 }
  

WCF Service Client(DemoLibrary): It calls the Search method of DemoService through proxy as displayed below

 public string GetResults(string s)
 {
     DemoServiceReference.DemoServiceClient client = null;
  
     try
     {
         client = new DemoServiceReference.DemoServiceClient();
         client.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;                
         client.ChannelFactory.Credentials.Windows.ClientCredential = System.Net.CredentialCache.DefaultNetworkCredentials;
         s = client.Search(s);
         return s;
     }
     finally
     {
         if (client != null)
         {
             if (client.State == CommunicationState.Opened)
             {
                 client.Close();
             }
             else if (client.State == CommunicationState.Faulted)
             {
                 client.Abort();
             }
         }
     }
 }
  

Adding Unit Tests for DemoLibrary:

In order to Unit Test WCF Service Client(DemoLibrary) project using Fakes and Visual Studio 11 the steps are

  1. Add a new Unit Test project as displayed below

    image

  2. In order to isolate the dependencies we need to add Fakes for DemoLibrary and System.ServiceModel assemblies and behaviour will be redefined using delegates. Add Fakes assembly as displayed below

    image

  3. A .fakes file will be added to the project under Fakes folder and reference to {Assembly}.Fakes.dll will be added as displayed below 

    image

  4. Similarly as explained above we need to generate Fakes for System.ServiceModel assembly. I got “Failed to generate Stub/Shim for type …” error messages on building the project. There are couple of ways to fix it.

    1. Change the Target Framework of the Test project from .NET Framework 4.5 to .NET Framework 4.0

    2. Open the System.ServiceModel.fakes and remove the types for which Shim/Stub generation is failing. Since I’m not using Stubs I’ve disabled the StubGeneration as displayed below

      image

  5. The next step is to Mock the Service call(redefine behaviour using delegates) and add Asserts as displayed in code snippets below. The main points are

    1. I have used Shims to isolate calls to non-virtual functions in unit test methods. Read more about Shim and Stub types.
    2. When using shim types in a unit test framework, you must wrap the test code in a ShimsContext to control the lifetime of your shims.
     [TestMethod]
     public void TestSearch()
     {
         using (ShimsContext.Create())
         {
             ShimWCFService<IDemoService>();
      
             ShimDemoServiceClient.Constructor = (var1) =>
             {
                 new ShimDemoServiceClient { };
             };
    
             ShimDemoServiceClient.AllInstances.SearchString = (var1, var2) =>
             {
                 return "Result";
             };
    
             Search search = new Search();
             string result = search.GetResults("test");
             Assert.IsNotNull(result);
             Assert.AreEqual(result, "Result");
         }
     }
      
     /// <summary>
     /// Mocks the WCF service.
     /// </summary>
     private void ShimWCFService<TService>() where TService : class
     {            
         ShimClientCredentials.Constructor = (var1) =>
         {
             new ShimClientCredentials();
         };
      
         ShimClientCredentials.AllInstances.WindowsGet = (var1) =>
         {
             return new ShimWindowsClientCredential();
         };
      
         ShimWindowsClientCredential.AllInstances.ClientCredentialGet = (var1) =>
         {
             return new System.Net.NetworkCredential();
         };
      
         ShimWindowsClientCredential.AllInstances.ClientCredentialSetNetworkCredential = (var1, var2) => { };
      
         ShimWindowsClientCredential.AllInstances.AllowNtlmGet = (var1) => { return true; };
      
         ShimWindowsClientCredential.AllInstances.AllowNtlmSetBoolean = (var1, var2) => { };
      
         ShimWindowsClientCredential.AllInstances.AllowedImpersonationLevelGet = (var1) => { return System.Security.Principal.TokenImpersonationLevel.Impersonation; };
      
         ShimWindowsClientCredential.AllInstances.AllowedImpersonationLevelSetTokenImpersonationLevel = (var1, var2) => { };
      
         ShimChannelFactory.AllInstances.CredentialsGet = (var1) =>
         { return new ShimClientCredentials(); };
      
         ShimClientBase<TService>.AllInstances.ClientCredentialsGet = (var1) =>
         {
             return new System.ServiceModel.Description.ClientCredentials();
         };
      
         ShimClientBase<TService>.AllInstances.ChannelFactoryGet = (var1) =>
         {
             return new ShimChannelFactory<TService>();
         };
      
         ShimClientBase<TService>.AllInstances.StateGet = (var1) =>
         {
             return CommunicationState.Opened;
         };
      
         ShimClientBase<TService>.AllInstances.Close = (var1) =>
         { };
      
         ShimClientBase<TService>.AllInstances.Abort = (var1) =>
         { };
     }
    
  6. Go to Unit Test explorer and run the tests

    image

  7. You can also Analyze the code coverage. The code coverage results are displayed below

    image

 

The sample is available for download @ Download Source code

Comments

  • Anonymous
    November 12, 2012
    The comment has been removed

  • Anonymous
    November 21, 2012
    There is already a thread social.msdn.microsoft.com/.../2ab9ef3e-2de2-40bc-871b-7cb42dbd2215 where I replied today. I replicated this issue in environment i.e. Win 8, VS 2010 & VS 2012. In order to drill down I created a new Unit Test project (new solution) and added reference to System.ServiceModel dll. I added Fakes assembly for System.ServiceModel reference. I ran the default unit test (empty test method) and it failed with same error message. I removed the System.ServiceModel.Fakes and instead added Fakes assembly for System. This time it worked. My initial thoughts are "Adding Fakes assembly for System.ServiceModel on Win 8 + VS 2010 + VS 2012 causes this error." I'll share if I find a workaround. Thanks.

  • Anonymous
    November 26, 2012
    I have found a fix for this issue. The workaround is Go to Test> Test Settings>Default Processor Architecture> and change to X64. In my case it was X86. I changed it to X64, rebuild and ran the test ... it worked :) I earlier suspected that "Adding Fakes assembly for System.ServiceModel on Win 8 + VS 2010 + VS 2012 causes this error.". While that was true however it wasn’t a solution.