FsUnit

In my earlier post on unit testing with xUnit I had difficulty with type conversion that led to some very ugly code. By moving away from .NET Core other, more functional, test libraries became available. The tool topping google search results was FsUnit which provides readable and fluent wrappers for the several major .NET unit testing frameworks.

I believe that parameterized tests are a valuable means of keeping code DRY and separating data from tests. However as my previous post noted, framework support for parameterized tests is very limited once the parameters fall outside the constant expression that can be placed in a .NET attribute. As an extension to existing libraries, using FsUnit means still having to deal with the capabilities of the underlying framework. FsUnit seems to integrate most natively with NUnit which makes NUnit the natural choice.

In the earlier post, there were challenges converting referenced data to and from objects because xUnit expects multiple parameters for one test to be presented as an obj[][], requiring ugly conversions to obj within each data set to allow the test framework to successfully cast the object back to the parameter type. NUnit is more flexible in this respect, requiring only an obj[]. After discarding tuples for readability and, in some cases type conversion, reasons, I found the clearest approach to creating an obj[] was to create a simple record:

type SampleTestNameData = { Tpl: DomainTypes.Template; Expected: Railway.Result<unit,TemplateCommandError> }
let src2 = [|   {
                    Tpl = ...
                    Expected = Railway.Success ()
            }|]

[<TestCaseSource("src2")>]
let SampleTestName (data: SampleTestNameData) =
    let res = Railway.Success ()
    Assert.That(data.Expected, Is.EqualTo(res))

Assert.That fails here because data.Expected has a different generic type to res: Railway.Result<unit,TemplateCommandError> vs Railway.Result<unit>. This can be fixed by replacing Assert.That with Assert.IsTrue((res = data.Expected)) which uses F# structural equality. This is probably not an issue in practice as types would be expected to be more fully specified and therefore match.

At this point we can introduce FsUnit to create nice readable syntax in our test: result |> should equal data.Expected.

Putting it all together looks something like this:

module AmApi.UnitTests.Template

open AmApi
open AmApi.DomainInterfaces
open AmApi.Commands.Template
open NUnit.Framework
open FsUnit

let basicTemplate() : DomainTypes.Template = { 
    Id = System.Guid.NewGuid()
    Name = "templateName"
    Fields = [{ Id =  System.Guid.NewGuid(); Name = "strFieldName"; Field = DomainTypes.StringField("strFieldValue") }] 
    MaintenanceProgramId = System.Guid.Empty 
}

let emptyRepo = { new ITemplateWriteRepository with
    member this.FindById id = None
    member this.Save (template: DomainTypes.Template) = ()
}

type CreateTemplateValidationData = { 
    Tpl: DomainTypes.Template; 
    Expected: Railway.Result 
}
let CreateTemplateValidationData1 = [|  
    {
        Tpl = basicTemplate()
        Expected = Railway.Success()
    };  {
        Tpl = { basicTemplate() with Fields = [] }
        Expected = Railway.Failure (InvalidTemplate "Template may not have an empty list")
    };  {
        Tpl = { basicTemplate() with Id = System.Guid.Empty }
        Expected = Railway.Failure (InvalidTemplate "Template must have an Id")
    }
|]

[<TestCaseSource("CreateTemplateValidationData1")>]
let ``Create template validation`` (data: CreateTemplateValidationData) =
    let cmd = TemplateCommand.Create(data.Tpl)
    let result : Railway.Result = TemplateCommandHandler.Execute cmd emptyRepo
    result |> should equal data.Expected