diff --git a/Consumer/Consumer.fsproj b/Consumer/Consumer.fsproj index 6f0f891..1646920 100644 --- a/Consumer/Consumer.fsproj +++ b/Consumer/Consumer.fsproj @@ -15,6 +15,7 @@ + diff --git a/Consumer/TestNonStatic.fs b/Consumer/TestNonStatic.fs new file mode 100644 index 0000000..0de83e6 --- /dev/null +++ b/Consumer/TestNonStatic.fs @@ -0,0 +1,31 @@ +namespace Consumer + +open System.Threading +open FsUnitTyped +open NUnit.Framework + +[] +module NonStaticTestHelpers = + let count = ref 0 + +[] +type TestNonStatic () = + let count = ref 0 + + member this.Thing = "i'm a thing" + + [] + member this.Foo () = + Interlocked.Increment NonStaticTestHelpers.count |> ignore + Interlocked.Increment count |> ignore + this.Thing |> shouldEqual "i'm a thing" + + [] + static member AnotherTest () = + Interlocked.Increment NonStaticTestHelpers.count |> ignore + 1 |> shouldEqual 1 + + [] + member _.TearDown () = + count.Value |> shouldEqual 1 + NonStaticTestHelpers.count.Value |> shouldEqual 2 diff --git a/TestRunner.Lib/TestFixture.fs b/TestRunner.Lib/TestFixture.fs index b8c9fa5..94c0bb9 100644 --- a/TestRunner.Lib/TestFixture.fs +++ b/TestRunner.Lib/TestFixture.fs @@ -26,6 +26,7 @@ module TestFixture = (setUp : MethodInfo list) (tearDown : MethodInfo list) (test : MethodInfo) + (containingObject : obj) (args : obj[]) : Result = @@ -40,7 +41,7 @@ module TestFixture = | head :: rest -> let result = try - head.Invoke (null, args) |> Ok + head.Invoke (containingObject, args) |> Ok with e -> Error (UserMethodFailure.Threw (head.Name, e)) @@ -103,6 +104,7 @@ module TestFixture = let private runTestsFromMember (setUp : MethodInfo list) (tearDown : MethodInfo list) + (containingObject : obj) (test : SingleTestMethod) : Result list = @@ -143,7 +145,10 @@ module TestFixture = match test.Kind, values with | TestKind.Data data, None -> data - |> Seq.map (fun args -> runOne setUp tearDown test.Method (Array.ofList args) |> normaliseError) + |> Seq.map (fun args -> + runOne setUp tearDown test.Method containingObject (Array.ofList args) + |> normaliseError + ) | TestKind.Data _, Some _ -> [ "Test has both the TestCase and Values attributes. Specify one or the other." @@ -151,7 +156,10 @@ module TestFixture = |> TestMemberFailure.Malformed |> Error |> Seq.singleton - | TestKind.Single, None -> runOne setUp tearDown test.Method [||] |> normaliseError |> Seq.singleton + | TestKind.Single, None -> + runOne setUp tearDown test.Method containingObject [||] + |> normaliseError + |> Seq.singleton | TestKind.Single, Some vals -> let combinatorial = Option.defaultValue Combinatorial.Combinatorial test.Combinatorial @@ -163,7 +171,8 @@ module TestFixture = |> Seq.toList |> List.combinations |> Seq.map (fun args -> - runOne setUp tearDown test.Method (Array.ofList args) |> normaliseError + runOne setUp tearDown test.Method containingObject (Array.ofList args) + |> normaliseError ) | Combinatorial.Sequential -> let maxLength = vals |> Seq.map (fun i -> i.Count) |> Seq.max @@ -174,7 +183,7 @@ module TestFixture = vals |> Array.map (fun param -> if i >= param.Count then null else param.[i].Value) - yield runOne setUp tearDown test.Method args |> normaliseError + yield runOne setUp tearDown test.Method containingObject args |> normaliseError } | TestKind.Source _, Some _ -> [ @@ -199,11 +208,12 @@ module TestFixture = for arg in args.GetValue null :?> System.Collections.IEnumerable do yield match arg with - | :? Tuple as (a, b) -> runOne setUp tearDown test.Method [| a ; b |] + | :? Tuple as (a, b) -> + runOne setUp tearDown test.Method containingObject [| a ; b |] | :? Tuple as (a, b, c) -> - runOne setUp tearDown test.Method [| a ; b ; c |] + runOne setUp tearDown test.Method containingObject [| a ; b ; c |] | :? Tuple as (a, b, c, d) -> - runOne setUp tearDown test.Method [| a ; b ; c ; d |] + runOne setUp tearDown test.Method containingObject [| a ; b ; c ; d |] | arg -> let argTy = arg.GetType () @@ -219,9 +229,14 @@ module TestFixture = if isNull argsMem then failwith "Unexpectedly could not call `.Arguments` on TestCaseData" - runOne setUp tearDown test.Method (argsMem.Invoke (arg, [||]) |> unbox) + runOne + setUp + tearDown + test.Method + containingObject + (argsMem.Invoke (arg, [||]) |> unbox) else - runOne setUp tearDown test.Method [| arg |] + runOne setUp tearDown test.Method containingObject [| arg |] |> normaliseError } ) @@ -253,9 +268,32 @@ module TestFixture = let run (filter : TestFixture -> SingleTestMethod -> bool) (tests : TestFixture) : int = eprintfn $"Running test fixture: %s{tests.Name} (%i{tests.Tests.Length} tests to run)" + let containingObject = + let methods = + seq { + match tests.OneTimeSetUp with + | None -> () + | Some t -> yield t + + match tests.OneTimeTearDown with + | None -> () + | Some t -> yield t + + yield! tests.Tests |> Seq.map (fun t -> t.Method) + } + + methods + |> Seq.tryPick (fun mi -> + if not mi.IsStatic then + Some (Activator.CreateInstance mi.DeclaringType) + else + None + ) + |> Option.toObj + match tests.OneTimeSetUp with | Some su -> - match su.Invoke (null, [||]) with + match su.Invoke (containingObject, [||]) with | :? unit -> () | ret -> failwith $"One-time setup procedure '%s{su.Name}' returned non-null %O{ret}" | _ -> () @@ -269,7 +307,7 @@ module TestFixture = eprintfn $"Running test: %s{test.Name}" let testSuccess = ref 0 - let results = runTestsFromMember tests.SetUp tests.TearDown test + let results = runTestsFromMember tests.SetUp tests.TearDown containingObject test for result in results do match result with @@ -287,7 +325,7 @@ module TestFixture = | Some td -> try // TODO: all these failwiths hide errors that we caught and wrapped up nicely above - if not (isNull (td.Invoke (null, [||]))) then + if not (isNull (td.Invoke (containingObject, [||]))) then failwith $"TearDown procedure '%s{td.Name}' returned non-null" with :? TargetInvocationException as e -> failwith $"One-time teardown of %s{td.Name} failed: %O{e.InnerException}"