diff --git a/docs/NUnit.fsx b/docs/NUnit.fsx index f6bb9be3..a74cb343 100644 --- a/docs/NUnit.fsx +++ b/docs/NUnit.fsx @@ -59,6 +59,38 @@ type MyTests () = member __.setup () = FSharpCustomMessageFormatter() |> ignore +(** +Type inference with generic values +--------------------------------- + +`should equal` accepts the actual value as `obj`, so the expected value's type +does not constrain the actual value's type. For example, an unannotated `Ok` +can become `Result` even when the expected value is +`Result`. These are different types and compare unequal, +although NUnit's formatted values can look identical. + +Use [FsUnitTyped](FsUnitTyped.html) to keep the expected and actual types linked: +*) +module ``Result type inference`` = + open FsUnitTyped + + type CommitMessage = { Type: string } + + let expectedTyped: Result<_, string> = Ok { Type = "feat" } + + Ok { Type = "feat" } |> shouldEqual expectedTyped + + (** + Alternatively, keep `should equal` and annotate the actual value: + *) + (Ok { Type = "feat" } : Result<_, string>) |> should equal expectedTyped + +(** +The same limitation applies to `Error` with an unconstrained success type. +Changing `should` to require matching types would break assertions such as +`[1] |> should contain 1`, where actual and expected intentionally differ in type. +*) + (** Euler - Problem 1 diff --git a/tests/FsUnit.NUnit.Test/typed.shouldEqualTests.fs b/tests/FsUnit.NUnit.Test/typed.shouldEqualTests.fs index 3d45ae08..798c0744 100644 --- a/tests/FsUnit.NUnit.Test/typed.shouldEqualTests.fs +++ b/tests/FsUnit.NUnit.Test/typed.shouldEqualTests.fs @@ -15,6 +15,8 @@ type NeverEqual() = override _.Equals(other) = false override _.GetHashCode() = 1 +type CommitMessage = { Type: string } + [] type ``shouldEqual Tests``() = @@ -91,6 +93,32 @@ type ``shouldEqual Tests``() = member _.``Error "Foo" should equal Error "Foo"``() = Error "Foo" |> shouldEqual(Error "Foo") + [] + member _.``Ok should infer the error type from the expected Result``() = + let expected: Result<_, string> = Ok { Type = "feat" } + Ok { Type = "feat" } |> shouldEqual expected + + [] + member _.``Error should infer the success type from the expected Result``() = + let expected: Result = Error "invalid" + Error "invalid" |> shouldEqual expected + + [] + member _.``Result with inferred error type should fail for a different payload``() = + let expected: Result<_, string> = Ok { Type = "feat" } + + (fun () -> Ok { Type = "fix" } |> shouldEqual expected) + |> Assert.Throws + |> ignore + + [] + member _.``Result with inferred error type should fail for a different case``() = + let expected: Result<_, string> = Ok { Type = "feat" } + + (fun () -> Error "invalid" |> shouldEqual expected) + |> Assert.Throws + |> ignore + [] member _.``Error "Foo" should equal fails and have same message``() = (fun () -> Error "Foo" |> shouldEqual(Error "Bar"))