FsUnit


What is FsUnit?

FsUnit is a set of libraries that makes unit-testing with F# more enjoyable. It adds a special syntax to your favorite .NET testing framework. FsUnit currently supports NUnit, xUnit, and MsTest (VS11 only).

The goals of FsUnit are:

  • to make unit-testing feel more at home in F# , i.e., more functional.
  • to leverage existing test frameworks while at the same time adapting them to the F# language in new ways.

NuGet packages are available for each of the supported testing frameworks:

Syntax

With FsUnit, you can write unit tests like this:

1: 
2: 
open NUnit.Framework
open FsUnit

One object equals or does not equal another:

1: 
2: 
1 |> should equal 1
1 |> should not' (equal 2)

One collection is equivalent or is not equivalent to another (order doesn't matter):

1: 
2: 
[2;4;6] |> should equivalent [4;6;2]
[2;4;6] |> should not' (equivalent [4;8;2])

One numeric object equals or does not equal another, with a specified tolerance:

1: 
2: 
10.1 |> should (equalWithin 0.1) 10.11
10.1 |> should not' ((equalWithin 0.001) 10.11)

A string does or does not start with or end with a specified substring:

1: 
2: 
3: 
4: 
5: 
6: 
"ships" |> should startWith "sh"
"ships" |> should not' (startWith "ss")
"ships" |> should endWith "ps"
"ships" |> should not' (endWith "ss")
"ships" |> should haveSubstring "hip"
"ships" |> should not' (haveSubstring "pip")

A List, Seq, or Array instance contains or does not contain a value:

1: 
2: 
[1] |> should contain 1
[] |> should not' (contain 1)

A List or Array instance has a certain length:

1: 
anArray |> should haveLength 4

A Collection instance has a certain count:

1: 
aCollection |> should haveCount 4

A function should throw a certain type of exception:

1: 
2: 
(fun () -> failwith "BOOM!" |> ignore) |> should throw typeof<System.Exception>
(fun () -> failwith "BOOM!" |> ignore) |> should (throwWithMessage "BOOM!") typeof<System.Exception>

A function should fail

1: 
shouldFail (fun () -> 5/0 |> ignore)

A number of assertions can be created using the be keyword:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
true |> should be True
false |> should not' (be True)

"" |> should be EmptyString
"" |> should be NullOrEmptyString

null |> should be NullOrEmptyString
null |> should be Null
null |> should be null

anObj |> should not' (be Null)
anObj |> should not' (be null)
anObj |> should be (sameAs anObj)
anObj |> should not' (be sameAs otherObj)

11 |> should be (greaterThan 10)
9 |> should not' (be greaterThan 10)
11 |> should be (greaterThanOrEqualTo 10)
9 |> should not' (be greaterThanOrEqualTo 10)
10 |> should be (lessThan 11)
10 |> should not' (be lessThan 9)
10.0 |> should be (lessThanOrEqualTo 10.1)
10 |> should not' (be lessThanOrEqualTo 9)

0.0 |> should be ofExactType<float>
1 |> should not' (be ofExactType<obj>)

[] |> should be Empty
[1] |> should not' (be Empty)

"test" |> should be instanceOfType<string>
"test" |> should not' (be instanceOfType<int>)

2.0 |> should not' (be NaN)

[1;2;3] |> should be unique

[1;2;3] |> should be ascending
[1;3;2] |> should not' (be ascending)
[3;2;1] |> should be descending
[3;1;2] |> should not' (be descending)

[1..10] |> should be (supersetOf [3;6;9])
[1..10] |> should not' (be supersetOf [5;11;21])

[3;6;9] |> should be (subsetOf [1..10])
[5;11;21] |> should not' (be subsetOf [1..10])

The ofCase operator allows you to check the case of a union. Supplying an expression that will result in a non-union type as well as supplying a non-union type as value argument will result in an exception detailing which parameter is wrong. Note that the actual value of the case is NOT checked, e.g. using <@ MyCase 5 @> as expression and (MyCase 10) as parameter will succeed. It is possible to check for more than one case by using a tuple of union cases.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
type TestUnion = First | Second of int | Third of string

First |> should be (ofCase<@ First @>) 
First |> should be (ofCase<@ First, Second @>) // checks if on the cases matches the given case
Second 5 |> should be (ofCase<@ Second 10 @>) // note, the actual value is not checked!
First |> should not' (be ofCase<@ Second 5 @>)
5 |> should be (ofCase<@ Second 5 @>) // will throw an exception
Second 5 |> should be (ofCase<@ int @>) // will throw an exception

Test Projects Targeting Higher F# Runtimes

If you build your test project with a target F# runtime greater than the targeted runtime of the FsUnit assembly, you may find FsUnit operators failing at runtime, in which case you need to add a binding redirect to the App.config file.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
<?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-999.999.999.999" newVersion="4.4.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

Contributing

The project is hosted on GitHub where you can report issues, fork the project and submit pull requests. If you're adding a new public API, please also consider adding samples that can be turned into a documentation. You might also want to read the library design notes to understand how it works.

Fork me on GitHub
val anArray : 'a list
val aCollection : System.Collections.Generic.List<int>
namespace System
namespace System.Collections
namespace System.Collections.Generic
Multiple items
type List<'T> =
  new : unit -> List<'T> + 2 overloads
  member Add : item:'T -> unit
  member AddRange : collection:IEnumerable<'T> -> unit
  member AsReadOnly : unit -> ReadOnlyCollection<'T>
  member BinarySearch : item:'T -> int + 2 overloads
  member Capacity : int with get, set
  member Clear : unit -> unit
  member Contains : item:'T -> bool
  member ConvertAll<'TOutput> : converter:Converter<'T, 'TOutput> -> List<'TOutput>
  member CopyTo : array:'T[] -> unit + 2 overloads
  ...
  nested type Enumerator

--------------------
System.Collections.Generic.List() : System.Collections.Generic.List<'T>
System.Collections.Generic.List(capacity: int) : System.Collections.Generic.List<'T>
System.Collections.Generic.List(collection: System.Collections.Generic.IEnumerable<'T>) : System.Collections.Generic.List<'T>
Multiple items
val int : value:'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
val anObj : obj
type obj = System.Object
val otherObj : obj
val failwith : message:string -> 'T
val ignore : value:'T -> unit
val typeof<'T> : System.Type
Multiple items
type Exception =
  new : unit -> Exception + 2 overloads
  member Data : IDictionary
  member GetBaseException : unit -> Exception
  member GetObjectData : info:SerializationInfo * context:StreamingContext -> unit
  member GetType : unit -> Type
  member HResult : int with get, set
  member HelpLink : string with get, set
  member InnerException : Exception
  member Message : string
  member Source : string with get, set
  ...

--------------------
System.Exception() : System.Exception
System.Exception(message: string) : System.Exception
System.Exception(message: string, innerException: exn) : System.Exception
Multiple items
val float : value:'T -> float (requires member op_Explicit)

--------------------
type float = System.Double

--------------------
type float<'Measure> = float
Multiple items
val string : value:'T -> string

--------------------
type string = System.String
type TestUnion =
  | First
  | Second of int
  | Third of string
union case TestUnion.First: TestUnion
union case TestUnion.Second: int -> TestUnion
union case TestUnion.Third: string -> TestUnion