Search code examples
f#assembly-binding-redirect

Why do I receive an error when initializing a record with an option?


Why do I receive an error when initializing a record with an option?

The following line fails my unit test:

let name =      { First=String20("Scott"); Last=String20("Nimrod"); Suffix=None }

Test Result:

Result StackTrace: at CreateModuleViewModel.Tests.submit module() Result Message: System.MissingMethodException : Method not found: 'Void Name..ctor(String20, String20, Microsoft.FSharp.Core.FSharpOption`1)'.

The test is as follows:

module CreateModuleViewModel.Tests

open FsUnit
open NUnit.Framework
open UILogic.State
open CreateModule.UILogic
open ManageModule.Entities

[<Test>]
let ``submit module`` () =

    // Setup
    let viewModel = CreationViewModel()

    let name =      { First=String20("Scott"); Last=String20("Nimrod"); Suffix=None }

    let duration =  { Hours=1; Minutes=30; Seconds=0 }
    let moduleItem = { Author=name; Duration=duration }

    // Tets
    viewModel.Add(moduleItem)

    // Verify
    viewModel.Modules.Head = moduleItem |> should equal true

The record definition is as follows:

type String20 = String20 of string

type Name = { 
    First:String20
    Last:String20
    Suffix:String20 option 
}

Why am I receiving this error?


Solution

  • The most common cause for the MissingMethodException is that some of your dependencies is compiled against a different version of FSharp.Core.dll than the unit test library.

    The way to solve this is to add bindingRedirect to your app.config. I think most unit test runners will respect the binding redirect too, and so this should fix the problem.

    Mark Seemann has a blog post about this. Stealing his example, you need something like:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
          <dependentAssembly>
            <assemblyIdentity name="FSharp.Core"
                              publicKeyToken="b03f5f7f11d50a3a"
                              culture="neutral"/>
            <bindingRedirect oldVersion="0.0.0.0-99.99.99.99"
                             newVersion="4.3.1.0"/>
          </dependentAssembly>
        </assemblyBinding>
      </runtime>
    </configuration>
    

    The newVersion will be either 4.3.1.0 (Visual Studio 2013) or 4.4.0.0 (Visual Studio 2015). I changed the oldVersion here to a range that should include all versions that may be around.

    The reason why this causes MethodMissingException is a bit subtle - but without the redirect, the runtime things that e.g. option<T> from one F# Core is not the same thing as option<T> from another version of F# Core and so it cannot find method that it was expecting.