Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom SpecFlow TestGeneratorProvider registration causes NullReferenceException at test generation

I'm trying to add my CodedUI test generator plugin in SpecFlow 1.9. I'm trying to use the new plugin registration, since the old type of registration causes all kinds of issues in our solution, since we prefer not to put the custom Generator Provider in the Specflow installation directory (which is different on each developers machine).

I started with adding the new Specflow.CustomPlugin NuGet package to a .NET 4.0 class library.

Based on the samples for the CodedUI Generator, I pieced this together:

using TechTalk.SpecFlow.Infrastructure;
[assembly: GeneratorPlugin(typeof(CodedUIGeneratorProvider.Generator.SpecFlowPlugin.CodedUIGeneratorPlugin))]

namespace CodedUIGeneratorProvider.Generator.SpecFlowPlugin
{
    using System.CodeDom;

    using BoDi;

    using TechTalk.SpecFlow.Generator;
    using TechTalk.SpecFlow.Generator.Configuration;
    using TechTalk.SpecFlow.Generator.Plugins;
    using TechTalk.SpecFlow.Generator.UnitTestProvider;
    using TechTalk.SpecFlow.UnitTestProvider;
    using TechTalk.SpecFlow.Utils;

    /// <summary>
    /// The CodedUI generator plugin.
    /// </summary>
    public class CodedUIGeneratorPlugin : IGeneratorPlugin
    {
        /// <summary>
        /// The register dependencies.
        /// </summary>
        /// <param name="container">
        /// The container.
        /// </param>
        public void RegisterDependencies(ObjectContainer container)
        {
        }

        /// <summary>
        /// The register customizations.
        /// </summary>
        /// <param name="container">
        /// The container.
        /// </param>
        /// <param name="generatorConfiguration">
        /// The generator configuration.
        /// </param>
        public void RegisterCustomizations(ObjectContainer container, SpecFlowProjectConfiguration generatorConfiguration)
        {
            container.RegisterTypeAs<CodedUIGeneratorProvider, IUnitTestGeneratorProvider>("default");
            container.RegisterTypeAs<MsTest2010RuntimeProvider, IUnitTestRuntimeProvider>("default");
        }

        /// <summary>
        /// The register configuration defaults.
        /// </summary>
        /// <param name="specFlowConfiguration">
        /// The spec flow configuration.
        /// </param>
        public void RegisterConfigurationDefaults(SpecFlowProjectConfiguration specFlowConfiguration)
        {
        }
    }

    /// <summary>
    /// The CodedUI generator.
    /// </summary>
    public class CodedUIGeneratorProvider : MsTest2010GeneratorProvider
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CodedUiGeneratorProvider"/> class.
        /// </summary>
        /// <param name="codeDomHelper">
        /// The code dom helper.
        /// </param>
        public CodedUIGeneratorProvider(CodeDomHelper codeDomHelper)
            : base(codeDomHelper)
        {
        }

        /// <summary>
        /// The set test class.
        /// </summary>
        /// <param name="generationContext">
        /// The generation context.
        /// </param>
        /// <param name="featureTitle">
        /// The feature title.
        /// </param>
        /// <param name="featureDescription">
        /// The feature description.
        /// </param>
        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")));
        }
    }
}

And when trying to configure it as follows:

  <specFlow>
    <!-- For additional details on SpecFlow configuration options see http://go.specflow.org/doc-config -->
    <plugins>
      <add name="CodedUIGeneratorProvider" path="." type="GeneratorAndRuntime"/>
    </plugins>
    <generator dependencies="CodedUIGeneratorProvider"/>
    <runtime dependencies="CodedUIGeneratorProvider"/>
  </specFlow>

I get the following error message:

error Generation error: SpecFlow configuration error -> The value of the property 'dependencies' cannot be parsed. The error is: Object reference not set to an instance of an object.

The error is caused inside the SpecFlow configuration code it seems, the stack trace is as follows:

 System.Configuration.dll!System.Configuration.ConfigurationProperty.ConvertFromString(string value) + 0x2a bytes
 System.Configuration.dll!System.Configuration.ConfigurationElement.DeserializePropertyValue(System.Configuration.ConfigurationProperty prop, System.Xml.XmlReader reader) + 0x36 bytes
 System.Configuration.dll!System.Configuration.ConfigurationElement.DeserializeElement(System.Xml.XmlReader reader, bool serializeCollectionKey) + 0x221 bytes
 System.Configuration.dll!System.Configuration.ConfigurationElement.DeserializeElement(System.Xml.XmlReader reader, bool serializeCollectionKey) + 0x78e bytes
 System.Configuration.dll!System.Configuration.ConfigurationSection.DeserializeSection(System.Xml.XmlReader reader) + 0x3c bytes
 TechTalk.SpecFlow.dll!TechTalk.SpecFlow.Configuration.ConfigurationSectionHandler.CreateFromXml(string xmlContent) + 0xb0 bytes
 TechTalk.SpecFlow.Generator.dll!TechTalk.SpecFlow.Generator.Configuration.GeneratorConfigurationProvider.LoadConfiguration(TechTalk.SpecFlow.Generator.Interfaces.SpecFlowConfigurationHolder configurationHolder, TechTalk.SpecFlow.Generator.Configuration.SpecFlowProjectConfiguration configuration) + 0x41 bytes
 TechTalk.SpecFlow.Vs2010Integration.dll!TechTalk.SpecFlow.Vs2010Integration.Generator.VsGeneratorInfoProvider.GenGeneratorConfig() + 0x52 bytes
 TechTalk.SpecFlow.Vs2010Integration.dll!TechTalk.SpecFlow.Vs2010Integration.Generator.VsGeneratorInfoProvider.GetGeneratorInfo() + 0x3a bytes
 TechTalk.SpecFlow.IdeIntegration.dll!TechTalk.SpecFlow.IdeIntegration.Generator.RemoteGeneratorServices.GetGeneratorInfo() Line 38 + 0x9 bytesC#
 TechTalk.SpecFlow.IdeIntegration.dll!TechTalk.SpecFlow.IdeIntegration.Generator.RemoteGeneratorServices.GetTestGeneratorFactoryForCreate() Line 43 + 0xa bytesC#
 TechTalk.SpecFlow.IdeIntegration.dll!TechTalk.SpecFlow.IdeIntegration.Generator.GeneratorServices.CreateTestGenerator() Line 24 + 0xa bytesC#
 TechTalk.SpecFlow.IdeIntegration.dll!TechTalk.SpecFlow.IdeIntegration.Generator.IdeSingleFileGenerator.GenerateCode(string inputFilePath, string inputFileContent,  TechTalk.SpecFlow.IdeIntegration.Generator.GeneratorServices generatorServices, TechTalk.SpecFlow.Generator.Interfaces.ProjectSettings projectSettings) Line 38 + 0x29 bytesC#
 TechTalk.SpecFlow.IdeIntegration.dll!TechTalk.SpecFlow.IdeIntegration.Generator.IdeSingleFileGenerator.Generate(string inputFilePath, string inputFileContent,  TechTalk.SpecFlow.IdeIntegration.Generator.GeneratorServices generatorServices, TechTalk.SpecFlow.Utils.CodeDomHelper codeDomHelper,  TechTalk.SpecFlow.Generator.Interfaces.ProjectSettings projectSettings) Line 23 + 0x12 bytesC#
 TechTalk.SpecFlow.IdeIntegration.dll!TechTalk.SpecFlow.IdeIntegration.Generator.IdeSingleFileGenerator.GenerateFile(string inputFilePath, string outputFilePath,  System.Func<TechTalk.SpecFlow.IdeIntegration.Generator.GeneratorServices> generatorServicesProvider, System.Func<string,string> inputFileContentProvider,  System.Action<string,string> outputFileContentWriter) Line 92 + 0x13 bytesC#
 TechTalk.SpecFlow.Vs2010Integration.dll!TechTalk.SpecFlow.VsIntegration.SingleFileGenerator.SpecFlowSingleFileGeneratorBase.GenerateInternal(string inputFilePath, string inputFileContent, EnvDTE.Project project, string defaultNamespace, System.Action<TechTalk.SpecFlow.VsIntegration.SingleFileGenerator.SingleFileGeneratorError> onError, out string generatedContent) + 0x18e bytes
 TechTalk.SpecFlow.Vs2010Integration.dll!TechTalk.SpecFlow.VsIntegration.SingleFileGenerator.SingleFileGeneratorBase.Generate(string inputFilePath, string inputFileContents, string defaultNamespace, System.IntPtr[] rgbOutputFileContents, out uint pcbOutput, Microsoft.VisualStudio.Shell.Interop.IVsGeneratorProgress generateProgress) + 0xc3 bytes
 [Native to Managed Transition]

Which reflector tells me can only mean that TechTalk.SpecFlow.IdeIntegration.Generator.RemoteGeneratorServices.generatorInfoProvider is null.

I'm at a loss on how to fix this. There's very few documentation available to resolve this.

The Question:

If I can get this to work I'd be very happy. altenatively I'd love to see a way to configure the 'old way' without having to put files in the SpecFlow installation directory.

like image 918
jessehouwing Avatar asked Apr 25 '13 18:04

jessehouwing


People also ask

What version of SpecFlow NuGet should I upgrade to?

We recommend upgrading your SpecFlow NuGet package to 2.4.1 or higher, where this is no longer an issue. When using the classic project system, the previous MSBuild target may no longer be located at the end of your project. NuGet ignores entries added manually. NuGet places the MSBuild imports at the end.

How do I disable specflowsinglefilegenerator in Visual Studio?

Remove all SpecFlowSingleFileGenerator custom tool entries from your feature files. Select Tools | Options | SpecFlow from the menu in Visual Studio, and set Enable SpecFlowSingleFileGenerator CustomTool to “false”.

What are the default options for using the SpecFlow targets file?

The TechTalk.SpecFlow.targets file defines a number of default options in the following section: ShowTrace: Set this to true to output trace information. OverwriteReadOnlyFiles: Set this to true to overwrite any read-only files in the target directory.

Do I need to add SpecFlow tools to my project?

After version SpecFlow 3.3.30 don’t need to add the SpecFlow.Tools.MSBuild.Generation package anymore to your project, if you are using one of our Unit-Test-Provider NuGet packages. Note: You will need at least VS2017/MSBuild 15 to use this package.


2 Answers

I found the issue :)

In the Register Customizations method of the plugin, don't register using a name, just use the RegisterTypeAs method:

public void RegisterCustomizations(ObjectContainer container, SpecFlowProjectConfiguration generatorConfiguration)
{
    container.RegisterTypeAs<CodedUIGeneratorProvider, IUnitTestGeneratorProvider>();
}

Then the config looks very simple, like this:

<specFlow>
  <!-- For additional details on SpecFlow configuration options see http://go.specflow.org/doc-config -->
  <plugins>
    <add name="CodedUIGeneratorProvider" path="." type="Generator"/>
  </plugins>
</specFlow>

This way you can drop the plugin assembly (which must be named *.SpecflowPlugin.dll) in the project directory, or use a relative path from the project directory and set it in the path=".\Lib" property of the plugins\add item.

For more information see: https://jessehouwing.net/specflow-custom-unit-test-generator/

like image 131
jessehouwing Avatar answered Sep 30 '22 14:09

jessehouwing


For developers coming here with SpecFlow 2.1.0, please add the following to the CodedUIGeneratorProvider class above:

    public void Initialize(GeneratorPluginEvents generatorPluginEvents, GeneratorPluginParameters generatorPluginParameters)
    {
        generatorPluginEvents.CustomizeDependencies += GeneratorPluginEvents_CustomizeDependencies;
    }

    private void GeneratorPluginEvents_CustomizeDependencies(object sender, CustomizeDependenciesEventArgs eventArgs)
    {
        eventArgs.ObjectContainer.RegisterTypeAs<CodedUIGeneratorProvider, IUnitTestGeneratorProvider>();
    }

The interface of the CustomPlugin interface has changed, making it necessary to hook up to customization events in order to get the customization registered.

See https://github.com/techtalk/SpecFlow/wiki/Plugins

like image 45
kroonwijk Avatar answered Sep 30 '22 12:09

kroonwijk