Custom SpecFlow Generator Plugin

The TechTalk.SpecFlow.CodedUI.MsTest class library project is where we will create our custom SpecFlow plugin, which SpecFlow will use to generate the [CodedUITest] attribute and work with our CodedUI tests.

By default, CodedUI works with the MsTest framework, where as SpecFlow supports the NUnit Framework out of the box. In order to get SpecFlow to work with CodedUI, we need to create a custom generator plugin that will allow SpecFlow to work with MsTest.

Creating the Custom Generator Plugin

Inherit from ‘MsTest2010GeneratorProvider’

The first thing we want to do is inherit from the ‘MsTest2010GeneratorProvider’ class by slightly tweaking the ‘SetTestClass’ method, as it is responsible for generating the code of the test class. Let’s override our ‘SetTestClass’ method below.

  1. Right-click on the ‘Class1.cs’ class file in the ‘TechTalk.SpecFlow.CodedUI.MsTest’ project and rename it to ‘CodedUiProviderPlugin.cs’
    • Click ‘Yes’ if a dialog box pops up
  2. Copy the following code and add the ‘CodedUiGeneratorProvider’ class (inheriting from ‘MsTest2010GeneratorProvider’) into the ‘TechTalk.SpecFlow.CodedUI.MsTest’ namespace…
    public class CodedUiGeneratorProvider : MsTest2010GeneratorProvider
    {
        public CodedUiGeneratorProvider(CodeDomHelper codeDomHelper) : base(codeDomHelper) { }
    
        public override void SetTestClass(TestClassGenerationContext generationContext, string featureTitle, string featureDescription)
        {
            base.SetTestClass(generationContext, featureTitle, featureDescription);
    
            foreach (CodeAttributeDeclaration declaration in generationContext.TestClass.CustomAttributes)
            {
                if (declaration.Name == "Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute")
                {
                    generationContext.TestClass.CustomAttributes.Remove(declaration);
                    break;
                }
            }
    
            generationContext.TestClass.CustomAttributes.Add(new CodeAttributeDeclaration(
            new CodeTypeReference("Microsoft.VisualStudio.TestTools.UITesting.CodedUITestAttribute")));
        }
    }

Creating the Plugin Itself

If we read the official documentation on SpecFlow plugins at https://github.com/techtalk/SpecFlow/wiki/Plugins, we can see that we need to register our test generator provider inside a CustomizeDependencies event handler.

Extending ‘IGeneratorPlugin’

  1. Next, let’s add the class that actually creates our SpecFlow plugin, by inheriting from ‘IGeneratorPlugin’. Three methods are brought in by the ‘IGeneratorPlugin’ interface
    • RegisterConfigurationDefaults – For intervening at the SpecFlow configuration stage itself
    • RegisterCustomizations – For extending any of the components of SpecFlow (this is what we will be using as we are extending ‘MsTest2010GeneratorProvider’)
    • RegisterDependencies – For when your plugin is of a complex nature and has it’s own dependencies, needing its own Composition Root set
  2. Copy the following code and add the ‘CodedUiProviderPlugin’ class (inheriting from IGeneratorPlugin) into the ‘TechTalk.SpecFlow.CodedUI.MsTest’ namespace…
    public class CodedUiProviderPlugin : IGeneratorPlugin
    {
        public void Initialize(GeneratorPluginEvents generatorPluginEvents, GeneratorPluginParameters generatorPluginParameters)
        {
            generatorPluginEvents.CustomizeDependencies += GeneratorPluginEvents_CustomizeDependencies;
        }
    
        private void GeneratorPluginEvents_CustomizeDependencies(object sender, CustomizeDependenciesEventArgs eventArgs)
        {
            eventArgs.ObjectContainer.RegisterTypeAs<CodedUiGeneratorProvider, IUnitTestGeneratorProvider>();
        }
    
        public void RegisterCustomizations(ObjectContainer container, SpecFlowProjectConfiguration generatorConfiguration)
        {
            string unitTestProviderName = generatorConfiguration.SpecFlowConfiguration.UnitTestProvider;
    
            if (unitTestProviderName.Equals("mstest", StringComparison.InvariantCultureIgnoreCase) ||
                unitTestProviderName.Equals("mstest.2010", StringComparison.InvariantCultureIgnoreCase))
            {
                container.RegisterTypeAs<CodedUiGeneratorProvider, IUnitTestGeneratorProvider>();              
            }
        }
    
        public void RegisterDependencies(ObjectContainer container) { }
    
        public void RegisterConfigurationDefaults(SpecFlowConfiguration specFlowConfiguration) { }
    }
  3. At the end, our class file as a whole should look like below…
    using System;
    using System.CodeDom;
    using BoDi;
    using TechTalk.SpecFlow.Configuration;
    using TechTalk.SpecFlow.Generator;
    using TechTalk.SpecFlow.Generator.Configuration;
    using TechTalk.SpecFlow.Generator.Plugins;
    using TechTalk.SpecFlow.Generator.UnitTestProvider;
    using TechTalk.SpecFlow.Utils;
    
    namespace TechTalk.SpecFlow.CodedUI.MsTest
    {
        public class CodedUiProviderPlugin : IGeneratorPlugin
        {
            public void Initialize(GeneratorPluginEvents generatorPluginEvents, GeneratorPluginParameters generatorPluginParameters)
            {
                generatorPluginEvents.CustomizeDependencies += GeneratorPluginEvents_CustomizeDependencies;
            }
    
            private void GeneratorPluginEvents_CustomizeDependencies(object sender, CustomizeDependenciesEventArgs eventArgs)
            {
                eventArgs.ObjectContainer.RegisterTypeAs<CodedUiGeneratorProvider, IUnitTestGeneratorProvider>();
            }
    
            public void RegisterCustomizations(ObjectContainer container, SpecFlowProjectConfiguration generatorConfiguration)
            {
                string unitTestProviderName = generatorConfiguration.SpecFlowConfiguration.UnitTestProvider;
    
                if (unitTestProviderName.Equals("mstest", StringComparison.InvariantCultureIgnoreCase) ||
                    unitTestProviderName.Equals("mstest.2010", StringComparison.InvariantCultureIgnoreCase))
                {
                    container.RegisterTypeAs<CodedUiGeneratorProvider, IUnitTestGeneratorProvider>();              
                }
            }
    
            public void RegisterDependencies(ObjectContainer container) { }
    
            public void RegisterConfigurationDefaults(SpecFlowConfiguration specFlowConfiguration) { }
        }
    
        public class CodedUiGeneratorProvider : MsTest2010GeneratorProvider
        {
            public CodedUiGeneratorProvider(CodeDomHelper codeDomHelper) : base(codeDomHelper) { }
    
            public override void SetTestClass(TestClassGenerationContext generationContext, string featureTitle, string featureDescription)
            {
                base.SetTestClass(generationContext, featureTitle, featureDescription);
    
                foreach (CodeAttributeDeclaration declaration in generationContext.TestClass.CustomAttributes)
                {
                    if (declaration.Name == "Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute")
                    {
                        generationContext.TestClass.CustomAttributes.Remove(declaration);
                        break;
                    }
                }
    
                generationContext.TestClass.CustomAttributes.Add(new CodeAttributeDeclaration(
                new CodeTypeReference("Microsoft.VisualStudio.TestTools.UITesting.CodedUITestAttribute")));
            }
        }
    }

In our plugin code above, we have coded the ‘RegisterCustomizations()’ method so that if our ‘unitTestProviderName’ that we provide in our App.Config (of our CodedUI Test Project) is ‘mstest’ or ‘mstest.2010’, then we register the custom type for that given interface.

Finishing Touches

Assembly Info

We now want to mark the assembly of the ‘Class Library’ project for our generator plugin, with the necessary attribute.

  1. Expand the ‘Properties’ of the ‘TechTalk.SpecFlow.CodedUi.MsTest’ project in the Solution Explorer
  2. Open up the ‘AssemblyInfo.cs’ file and add the following attribute…
    [assembly: GeneratorPlugin(typeof(CodedUiProviderPlugin))]

Assembly Name

Before we are able to build our generator plugin and use it, we need to take care of a small SpecFlow problem. In order for SpecFlow to be able to load our plugin, we need to add a suffix of “.SpecFlowPlugin” to our Assembly Name.

  1. Right-click on the ‘TechTalk.SpecFlow.CodedUi.MsTest’ project in the Solution Explorer and select ‘Properties’
  2. Click the ‘Application’ tab if not selected already
  3. Add the suffix of “.SpecFlowPlugin” to the end of the current ‘Assembly name:’, so it reads something like…
    TechTalk.SpecFlow.CodedUI.MsTest.SpecFlowPlugin

App.config

We now want to change the Unit Test provider in our App.config to use the custom test generator that we have just made

  1. Open up the ‘App.Config’ file in the CodedUI Test Project (create one if necessary by right-clicking on the Coded UI Test project in the Solution Explorer and selecting Add –> New Item –> App configuration file)
  2. Change the <unitTestProvider> in App.config to use the new generator plugin.  It should look similar to the example below…
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <configSections>
        <section name="specFlow" type="TechTalk.SpecFlow.Configuration.ConfigurationSectionHandler, TechTalk.SpecFlow" />
      </configSections>
      <specFlow>
        <unitTestProvider name="MsTest.2010" />
      </specFlow>
    </configuration>

     

Digiprove sealCopyright secured by Digiprove © 2018
Liked it? Take a second to support Thomas on Patreon!

Previous Article

Next Article

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.