2

I'm writing unit tests in F# using MSTest, and I'd like to write tests that assert that an exception is raised. The two methods that I can find for doing this are either (1) write the tests in C# or (2) don't use MSTest, or add another test package, like xunit, on top of it. Neither of these is an option for me. The only thing I can find on this is in the MSDN docs, but that omits F# examples.

Using F# and MSTest, how do I assert that a particular call raises a particular exception?

daazc
  • 23
  • 2

2 Answers2

3

MSTest has an ExpectedExceptionAttribute that can be used, but it is a less than ideal way to test an exception has been thrown because it doesn't let you assert the specific call that should throw. If any method in the test method throws the expected exception type, then the test passes. This can be bad with commonly used exception types like InvalidOperationException. I use MSTest a lot and we have a Throws helper method for this in our own AssertHelper class (in C#). F# will let you put it into an Assert module so that it appears with all the other Assert methods in intellisense, which is pretty cool:

namespace FSharpTestSpike

open System
open Microsoft.VisualStudio.TestTools.UnitTesting

module Assert =

  let Throws<'a> f =
    let mutable wasThrown = false
    try
      f()
    with
    | ex -> Assert.AreEqual(ex.GetType(), typedefof<'a>, (sprintf "Actual Exception: %A" ex)); wasThrown <- true

    Assert.IsTrue(wasThrown, "No exception thrown")

[<TestClass>]
type MyTestClass() =     

  [<TestMethod>]
  member this.``Expects an exception and thrown``() =
    Assert.Throws<InvalidOperationException> (fun () -> InvalidOperationException() |> raise)

  [<TestMethod>]
  member this.``Expects an exception and not thrown``() =
    Assert.Throws<InvalidOperationException> (fun () -> ())

  [<TestMethod>]
  member this.``Expects an InvalidOperationException and a different one is thrown``() =
    Assert.Throws<InvalidOperationException> (fun () -> Exception("BOOM!") |> raise)
Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
  • Good point about the `ExpectedException` attribute. But I would change the implementation of `Throws` so the stack traces of unexpected exceptions are not swallowed: http://pastebin.com/gefukSj6 – Nikon the Third Sep 24 '13 at 21:37
1

Like this?

namespace Tests

open Microsoft.VisualStudio.TestTools.UnitTesting
open System

[<TestClass>]
type SomeTests () =

    [<TestMethod; ExpectedException (typeof<InvalidOperationException>)>]
    member this.``Test that expects InvalidOperationException`` () =
        InvalidOperationException () |> raise |> ignore
Nikon the Third
  • 2,771
  • 24
  • 35
  • This is how I tried to do it, but adding the ExpectedException attribute to a test method prevented test discovery from finding it. But your code works, so I must have had a mistake in my code. Does this exist somewhere in the MSDN documentation and I just missed it? Many thanks! – daazc Sep 26 '13 at 16:55
  • 1
    You have to make sure the test method returns void (`Unit`). Simply ending the method with a `raise` call makes it generic since `raise` returns `'a`. This is what the call to `ignore` is for. Another possibility would be to define the test method like this: `member this.XYZ () : Unit = ...`. – Nikon the Third Sep 28 '13 at 05:41