Skip to content

Commit

Permalink
Allow xUnit Properties attribute to be used at assembly level (#559)
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurenceJKing authored Apr 6, 2021
1 parent 017743d commit 1e0ad9a
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 19 deletions.
24 changes: 20 additions & 4 deletions docs/RunningTests.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,23 +148,39 @@ instance just for that test method. You can also use the `PropertiesAttribute` (
type Positive =
static member Double() =
Arb.Default.Float()
|> Arb.mapFilter abs (fun t -> t >= 0.0)
|> Arb.mapFilter abs (fun t -> t > 0.0)
type Negative =
static member Double() =
Arb.Default.Float()
|> Arb.mapFilter (abs >> ((-) 0.0)) (fun t -> t <= 0.0)
|> Arb.mapFilter (abs >> ((-) 0.0)) (fun t -> t < 0.0)
type Zero =
static member Double() =
0.0
|> Gen.constant
|> Arb.fromGen
[<assembly: Properties( Arbitrary = [| typeof<Zero> |])>] do()
module ModuleWithoutProperties =
[<Property>]
let ``should use Arb instances from assembly``(underTest:float) =
underTest = 0.0
[<Property( Arbitrary=[| typeof<Positive> |] )>]
let ``should use Arb instance on method``(underTest:float) =
underTest > 0.0
[<Properties( Arbitrary=[| typeof<Negative> |] )>]
module ModuleWithProperties =
[<Property>]
let ``should use Arb instances from enclosing module``(underTest:float) =
underTest <= 0.0
underTest < 0.0
[<Property( Arbitrary=[| typeof<Positive> |] )>]
let ``should use Arb instance on method``(underTest:float) =
underTest >= 0.0
underTest > 0.0
(**
Using `PropertiesAttribute` and `PropertyAttribute` you can set any configuration. For example in following module:
Expand Down
29 changes: 14 additions & 15 deletions src/FsCheck.Xunit/PropertyAttribute.fs
Original file line number Diff line number Diff line change
Expand Up @@ -148,33 +148,32 @@ type public PropertyAttribute() =
member internal __.Config = config

///Set common configuration for all properties within this class or module
[<AttributeUsage(AttributeTargets.Class, AllowMultiple = false)>]
[<AttributeUsage(AttributeTargets.Class ||| AttributeTargets.Assembly, AllowMultiple = false)>]
type public PropertiesAttribute() = inherit PropertyAttribute()

/// The xUnit2 test runner for the PropertyAttribute that executes the test via FsCheck
type PropertyTestCase(diagnosticMessageSink:IMessageSink, defaultMethodDisplay:TestMethodDisplay, testMethod:ITestMethod, ?testMethodArguments:obj []) =
type PropertyTestCase(diagnosticMessageSink:IMessageSink, defaultMethodDisplay:TestMethodDisplay, testMethod:ITestMethod, ?testMethodArguments:obj []) =
inherit XunitTestCase(diagnosticMessageSink, defaultMethodDisplay, testMethod, (match testMethodArguments with | None -> null | Some v -> v))

let combineAttributes (attributes: (IAttributeInfo option) list) =
attributes
|> List.choose id
|> List.map(fun attr -> attr.GetNamedArgument<PropertyConfig> "Config")
|> List.reduce(fun higherLevelAttribute lowerLevelAttribute ->
PropertyConfig.combine lowerLevelAttribute higherLevelAttribute)

new() = new PropertyTestCase(null, TestMethodDisplay.ClassAndMethod, null)

member this.Init(output:TestOutputHelper) =
let factAttribute = this.TestMethod.Method.GetCustomAttributes(typeof<PropertyAttribute>) |> Seq.head
let arbitrariesOnClass =
this.TestMethod.TestClass.Class.GetCustomAttributes(Type.GetType("FsCheck.Xunit.ArbitraryAttribute"))
|> Seq.collect (fun attr -> attr.GetNamedArgument "Arbitrary")
|> Seq.toArray
let generalAttribute =
this.TestMethod.TestClass.Class.GetCustomAttributes(typeof<PropertiesAttribute>)
|> Seq.tryFind (fun _ -> true)

let config =
match generalAttribute with
| Some generalAttribute ->
PropertyConfig.combine
(factAttribute.GetNamedArgument "Config")
(generalAttribute.GetNamedArgument "Config")
| None ->
factAttribute.GetNamedArgument "Config"

let config = combineAttributes [
this.TestMethod.TestClass.Class.Assembly.GetCustomAttributes(typeof<PropertiesAttribute>) |> Seq.tryHead
this.TestMethod.TestClass.Class.GetCustomAttributes(typeof<PropertiesAttribute>) |> Seq.tryHead
this.TestMethod.Method.GetCustomAttributes(typeof<PropertyAttribute>) |> Seq.head |> Some]

{ config with Arbitrary = Array.append config.Arbitrary arbitrariesOnClass }
|> PropertyConfig.toConfig output
Expand Down
1 change: 1 addition & 0 deletions tests/FsCheck.Test/FsCheck.Test.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<DefineConstants>DEBUG</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Include="Fscheck.XUnit\PropertyAttributeTests.fs" />
<Compile Include="Helpers.fs" />
<Compile Include="Random.fs" />
<Compile Include="TypeClass.fs" />
Expand Down
69 changes: 69 additions & 0 deletions tests/FsCheck.Test/Fscheck.XUnit/PropertyAttributeTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace Fscheck.Test.FsCheck.XUnit.PropertyAttribute
open FsCheck.Xunit
open FsCheck

type AttributeLevel =
| Assembly
| ClassOrModule
| NestedClassOrModule
| MethodOrProperty

type AttributeLevel_Assembly() =
static member Generator =
Assembly
|> Gen.constant
|> Arb.fromGen

type AttributeLevel_ClassOrModule() =
static member Generator =
ClassOrModule
|> Gen.constant
|> Arb.fromGen

type AttributeLevel_MethodOrProperty() =
static member Generator =
MethodOrProperty
|> Gen.constant
|> Arb.fromGen

type AttributeLevel_NestedClassOrModule() =
static member Generator =
NestedClassOrModule
|> Gen.constant
|> Arb.fromGen

[<assembly: Properties(Arbitrary = [| typeof<AttributeLevel_Assembly> |])>]
do()

module ``when module does not have properties attribute``=
[<Property>]
let ``then the assembly attribute should be used`` = function
| Assembly -> true
| _ -> false

[<Property(Arbitrary = [| typeof<AttributeLevel_MethodOrProperty>|])>]
let ``then the property attribute takes precient`` = function
| MethodOrProperty -> true
| _ -> false

[<Properties(Arbitrary = [|typeof<AttributeLevel_ClassOrModule>|])>]
module ``when module has properties attribute`` =

[<Property>]
let ``then the module's property takes precident`` = function
| ClassOrModule -> true
| _ -> false

[<Property(Arbitrary = [| typeof<AttributeLevel_MethodOrProperty>|])>]
let ``then the property attribute takes precient`` = function
| MethodOrProperty -> true
| _ -> false

[<Properties(Arbitrary = [|typeof<AttributeLevel_NestedClassOrModule>|])>]
module ``and there is and nested module`` =
[<Property>]
let ``then the nested module's property takes precident`` = function
| NestedClassOrModule -> true
| _ -> false


0 comments on commit 1e0ad9a

Please sign in to comment.