diff --git a/Directory.Packages.props b/Directory.Packages.props
index 543f6d23..75a04975 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -38,5 +38,6 @@
     <PackageVersion Include="xunit.extensibility.core" Version="[2.4.1, 3.0.0)" />
     <PackageVersion Include="xunit.extensibility.execution" Version="[2.4.1, 3.0.0)" />
     <PackageVersion Include="xunit.runner.visualstudio" Version="3.0.0" />
+    <PackageVersion Include="xunit.v3.extensibility.core" Version="2.0.0" />
   </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/FsCheck.sln b/FsCheck.sln
index 97d79f45..4b667080 100644
--- a/FsCheck.sln
+++ b/FsCheck.sln
@@ -1,4 +1,4 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
+Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio Version 17
 VisualStudioVersion = 17.9.34616.47
 MinimumVisualStudioVersion = 10.0.40219.1
@@ -59,6 +59,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharp.DocSnippets", "examp
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FsCheck.Test.CSharp", "tests\FsCheck.Test.CSharp\FsCheck.Test.CSharp.csproj", "{86955D54-4D54-4D4C-A7DC-F98F4CCFE498}"
 EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsCheck.Xunit.v3", "src\FsCheck.Xunit.v3\FsCheck.Xunit.v3.fsproj", "{CADBF086-2B3D-42D5-8A66-F9ACC6119ED0}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -113,6 +115,10 @@ Global
 		{86955D54-4D54-4D4C-A7DC-F98F4CCFE498}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{86955D54-4D54-4D4C-A7DC-F98F4CCFE498}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{86955D54-4D54-4D4C-A7DC-F98F4CCFE498}.Release|Any CPU.Build.0 = Release|Any CPU
+		{CADBF086-2B3D-42D5-8A66-F9ACC6119ED0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{CADBF086-2B3D-42D5-8A66-F9ACC6119ED0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{CADBF086-2B3D-42D5-8A66-F9ACC6119ED0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{CADBF086-2B3D-42D5-8A66-F9ACC6119ED0}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/src/FsCheck.Xunit.v3/AssemblyInfo.fs b/src/FsCheck.Xunit.v3/AssemblyInfo.fs
new file mode 100644
index 00000000..e658dfdc
--- /dev/null
+++ b/src/FsCheck.Xunit.v3/AssemblyInfo.fs
@@ -0,0 +1,23 @@
+
+// Auto-generated; edits may be deleted at any time
+namespace System
+open System.Reflection
+open System.Runtime.CompilerServices
+
+[<assembly: AssemblyTitle("FsCheck.Xunit.v3")>]
+[<assembly: AssemblyProduct("FsCheck.Xunit.v3")>]
+[<assembly: AssemblyDescription("Integrates FsCheck with xUnit.v3.NET")>]
+[<assembly: AssemblyVersion("3.1.0")>]
+[<assembly: AssemblyFileVersion("3.1.0")>]
+[<assembly: AssemblyKeyFile("../../FsCheckKey.snk")>]
+[<assembly: InternalsVisibleTo("FsCheck.Test")>]
+do ()
+
+module internal AssemblyVersionInformation =
+    let [<Literal>] AssemblyTitle = "FsCheck.Xunit.v3"
+    let [<Literal>] AssemblyProduct = "FsCheck.Xunit.v3"
+    let [<Literal>] AssemblyDescription = "Integrates FsCheck with xUnit.v3.NET"
+    let [<Literal>] AssemblyVersion = "3.1.0"
+    let [<Literal>] AssemblyFileVersion = "3.1.0"
+    let [<Literal>] AssemblyKeyFile = "../../FsCheckKey.snk"
+    let [<Literal>] InternalsVisibleTo = "FsCheck.Test"
diff --git a/src/FsCheck.Xunit.v3/CheckExtensions.fs b/src/FsCheck.Xunit.v3/CheckExtensions.fs
new file mode 100644
index 00000000..811d46f2
--- /dev/null
+++ b/src/FsCheck.Xunit.v3/CheckExtensions.fs
@@ -0,0 +1,60 @@
+namespace FsCheck.Xunit
+
+open System
+open System.Runtime.CompilerServices
+open FsCheck
+open global.Xunit
+
+module private Helper =
+    let private runner (testOutputHelper: ITestOutputHelper) =
+        { new IRunner with
+            member __.OnStartFixture t =
+                Runner.onStartFixtureToString t |> testOutputHelper.WriteLine
+            member __.OnArguments (ntest,args, every) =
+                every ntest args |> testOutputHelper.WriteLine
+            member __.OnShrink(args, everyShrink) =
+                everyShrink args |> testOutputHelper.WriteLine
+            member __.OnFinished(name,testResult) = 
+                Runner.onFinishedToString name testResult |> testOutputHelper.WriteLine
+        }
+
+    let private throwingRunner (testOutputHelper: ITestOutputHelper) =
+        { new IRunner with
+            member __.OnStartFixture t =
+                testOutputHelper.WriteLine (Runner.onStartFixtureToString t)
+            member __.OnArguments (ntest,args, every) =
+                testOutputHelper.WriteLine (every ntest args)
+            member __.OnShrink(args, everyShrink) =
+                testOutputHelper.WriteLine (everyShrink args)
+            member __.OnFinished(name,testResult) = 
+                match testResult with
+                | FsCheck.TestResult.Passed _ -> testOutputHelper.WriteLine (Runner.onFinishedToString name testResult)
+                | _ -> failwithf "%s" (Runner.onFinishedToString name testResult)
+        }
+
+    let writeToXunit (config:Config) (testOutputHelper: ITestOutputHelper) =
+        config.WithRunner(runner testOutputHelper)
+
+    let writeToXunitThrow (config:Config) (testOutputHelper: ITestOutputHelper) =
+        config.WithRunner(throwingRunner testOutputHelper)
+
+[<AbstractClass;Sealed;Extension>]
+type CheckExtensions =
+    [<Extension>]
+    static member QuickCheck(property:Property, testOutputHelper: ITestOutputHelper) =
+        Check.One(Helper.writeToXunit Config.Quick testOutputHelper,property)
+    [<Extension>]
+    static member QuickCheck(property:Property, testName:string, testOutputHelper: ITestOutputHelper) =
+        Check.One(testName,Helper.writeToXunit Config.Quick testOutputHelper,property)
+    [<Extension>]
+    static member QuickCheckThrowOnFailure(property:Property, testOutputHelper: ITestOutputHelper) =
+        Check.One(Helper.writeToXunitThrow Config.QuickThrowOnFailure testOutputHelper,property)
+    [<Extension>]
+    static member VerboseCheck(property:Property, testOutputHelper: ITestOutputHelper) =
+        Check.One(Helper.writeToXunit Config.Verbose testOutputHelper, property)
+    [<Extension>]
+    static member VerboseCheck(property:Property, testName:string, testOutputHelper: ITestOutputHelper) =
+        Check.One(testName, Helper.writeToXunit Config.Verbose testOutputHelper, property)
+    [<Extension>]
+    static member VerboseCheckThrowOnFailure(property:Property, testOutputHelper: ITestOutputHelper) =
+        Check.One(Helper.writeToXunitThrow Config.VerboseThrowOnFailure testOutputHelper,property)
\ No newline at end of file
diff --git a/src/FsCheck.Xunit.v3/FsCheck.Xunit.v3.fsproj b/src/FsCheck.Xunit.v3/FsCheck.Xunit.v3.fsproj
new file mode 100644
index 00000000..f3f619b7
--- /dev/null
+++ b/src/FsCheck.Xunit.v3/FsCheck.Xunit.v3.fsproj
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <AssemblyName>FsCheck.Xunit</AssemblyName>
+        <TargetFrameworks>netstandard2.0;net462</TargetFrameworks>
+        <GenerateDocumentationFile>true</GenerateDocumentationFile>
+        <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+        <PackageDescription>
+            FsCheck.Xunit integrates FsCheck with xUnit.v3.NET by adding a PropertyAttribute that runs FsCheck tests, similar to xUnit.v3.NET's FactAttribute.
+
+            All the options normally available in vanilla FsCheck via configuration can be controlled via the PropertyAttribute.
+        </PackageDescription>
+        <PackageTags>$(PackageTags);xunit;xunit.net</PackageTags>
+    </PropertyGroup>
+    <ItemGroup>
+        <Compile Include="AssemblyInfo.fs"/>
+        <Compile Include="Runner.fs"/>
+        <Compile Include="PropertyAttribute.fs"/>
+        <Compile Include="CheckExtensions.fs"/>
+    </ItemGroup>
+    <ItemGroup>
+        <PackageReference Include="FSharp.Core"/>
+        <PackageReference Include="xunit.v3.extensibility.core" />
+    </ItemGroup>
+    <ItemGroup>
+        <ProjectReference Include="../FsCheck/FsCheck.fsproj"/>
+    </ItemGroup>
+</Project>
diff --git a/src/FsCheck.Xunit.v3/PropertyAttribute.fs b/src/FsCheck.Xunit.v3/PropertyAttribute.fs
new file mode 100644
index 00000000..c78f6188
--- /dev/null
+++ b/src/FsCheck.Xunit.v3/PropertyAttribute.fs
@@ -0,0 +1,314 @@
+namespace FsCheck.Xunit.v3
+
+open System
+open System.Reflection
+open System.Threading.Tasks
+
+open Xunit
+open Xunit.Sdk
+
+open FsCheck
+open global.Xunit.v3
+
+type PropertyFailedException =
+    inherit Exception
+    new (testResult:FsCheck.TestResult) = {
+        inherit Exception(sprintf "%s%s" Environment.NewLine (Runner.onFinishedToString "" testResult)) }
+    new (userMessage, innerException : exn) = {
+        inherit Exception(userMessage, innerException) }
+
+//can not be an anonymous type because of let mutable.
+type XunitRunner() =
+    let mutable result = None
+    member __.Result = result.Value
+    interface IRunner with
+        override __.OnStartFixture _ = ()
+        override __.OnArguments (ntest,args, every) =
+            every ntest args |> ignore
+        override __.OnShrink(args, everyShrink) =
+            everyShrink args |> ignore
+        override __.OnFinished(_ ,testResult) =
+            result <- Some testResult
+
+type internal PropertyConfig =
+    { MaxTest        : Option<int>
+      MaxRejected    : Option<int>
+      Replay         : Option<string>
+      Parallelism    : Option<int>
+      StartSize      : Option<int>
+      EndSize        : Option<int>
+      Verbose        : Option<bool>
+      QuietOnSuccess : Option<bool>
+      Arbitrary      : Type[] }
+
+[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
+module internal PropertyConfig = 
+    let orElse y = function
+        | Some x -> Some x
+        | None   -> y
+
+    let orDefault x y = defaultArg y x
+
+    let zero =
+        { MaxTest        = None
+          MaxRejected    = None
+          Replay         = None
+          Parallelism    = None
+          StartSize      = None
+          EndSize        = None
+          Verbose        = None
+          QuietOnSuccess = None
+          Arbitrary      = [||] }
+
+    let combine extra original =
+        { MaxTest        = extra.MaxTest        |> orElse original.MaxTest
+          MaxRejected    = extra.MaxRejected    |> orElse original.MaxRejected
+          Replay         = extra.Replay         |> orElse original.Replay
+          Parallelism    = extra.Parallelism    |> orElse original.Parallelism
+          StartSize      = extra.StartSize      |> orElse original.StartSize
+          EndSize        = extra.EndSize        |> orElse original.EndSize
+          Verbose        = extra.Verbose        |> orElse original.Verbose
+          QuietOnSuccess = extra.QuietOnSuccess |> orElse original.QuietOnSuccess
+          Arbitrary      = Array.append extra.Arbitrary original.Arbitrary }
+
+    let parseReplay (str: string) =
+        //if someone sets this, we want it to throw if it fails
+        let split = str.Trim('(',')').Split([|","|], StringSplitOptions.RemoveEmptyEntries)
+        let seed = UInt64.Parse(split.[0])
+        let gamma = UInt64.Parse(split.[1])
+        let size = if split.Length = 3 then Some <| Convert.ToInt32(UInt32.Parse(split.[2])) else None
+        { Rnd = Rnd (seed,gamma); Size = size }
+
+    let toConfig (output : TestOutputHelper) propertyConfig =
+        Config.Default
+              .WithReplay(
+                  propertyConfig.Replay
+                  |> Option.map parseReplay
+                  |> orElse Config.Default.Replay
+              )
+              .WithParallelRunConfig(
+                  propertyConfig.Parallelism
+                  |> Option.map (fun i -> { MaxDegreeOfParallelism = i })
+                  |> orElse Config.Default.ParallelRunConfig
+              )
+              .WithMaxTest(propertyConfig.MaxTest |> orDefault Config.Default.MaxTest)
+              .WithMaxRejected(propertyConfig.MaxRejected |> orDefault Config.Default.MaxRejected)
+              .WithStartSize(propertyConfig.StartSize |> orDefault Config.Default.StartSize)
+              .WithEndSize(propertyConfig.EndSize |> orDefault Config.Default.EndSize)
+              .WithQuietOnSuccess(propertyConfig.QuietOnSuccess |> orDefault Config.Default.QuietOnSuccess)
+              .WithArbitrary(Seq.toList propertyConfig.Arbitrary)
+              .WithRunner(XunitRunner())
+              .WithEvery(
+                  if propertyConfig.Verbose |> Option.exists id then 
+                      fun n args -> output.WriteLine (Config.Verbose.Every n args); ""
+                  else 
+                      Config.Quick.Every
+              )
+              .WithEveryShrink(
+                  if propertyConfig.Verbose |> Option.exists id then 
+                      fun args -> output.WriteLine (Config.Verbose.EveryShrink args); ""
+                  else 
+                      Config.Quick.EveryShrink
+              )
+
+///Run this method as an FsCheck test.
+[<AttributeUsage(AttributeTargets.Method ||| AttributeTargets.Property, AllowMultiple = false)>]
+[<XunitTestCaseDiscoverer(typeof<PropertyDiscoverer>)>]
+type public PropertyAttribute() =
+    inherit FactAttribute()
+    let mutable config = PropertyConfig.zero
+    let mutable replay = null
+    let mutable parallelism = -1
+    let mutable maxTest = -1
+    let mutable maxRejected = -1
+    let mutable startSize = -1
+    let mutable endSize = -1
+    let mutable verbose = false
+    let mutable arbitrary = [||]
+    let mutable quietOnSuccess = false
+    
+    ///If set, the seed to use to start testing. Allows reproduction of previous runs. You can just paste
+    ///the tuple from the output window, e.g. 12344,12312 or (123,123).
+    ///Additionally, you can also specify a start size as the third parameter, e.g. 12344,12312,10 or (123,123,10).
+    member __.Replay with get() = replay and set(v) = replay <- v; config <- {config with Replay = if String.IsNullOrEmpty v then None else Some v}
+    ///If set, run tests in parallel. Useful for Task/async related work and heavy number crunching
+    ///Environment.ProcessorCount have been found to be useful default.
+    member __.Parallelism with get() = parallelism and set(v) = parallelism <- v; config <- {config with Parallelism = Some v}
+    ///The maximum number of tests that are run.
+    member __.MaxTest with get() = maxTest and set(v) = maxTest <- v; config <- {config with MaxTest = Some v}
+    ///The maximum number of tests where values are rejected, e.g. as the result of ==>
+    member __.MaxRejected with get() = maxRejected and set(v) = maxRejected <- v; config <- {config with MaxRejected = Some v}
+    ///The size to use for the first test.
+    member __.StartSize with get() = startSize and set(v) = startSize <- v; config <- {config with StartSize = Some v}
+    ///The size to use for the last test, when all the tests are passing. The size increases linearly between Start- and EndSize.
+    member __.EndSize with get() = endSize and set(v) = endSize <- v; config <- {config with EndSize = Some v}
+    ///Output all generated arguments.
+    member __.Verbose with get() = verbose and set(v) = verbose <- v; config <- {config with Verbose = Some v}
+    ///The Arbitrary instances to use for this test method. The Arbitrary instances
+    ///are merged in back to front order i.e. instances for the same generated type
+    ///at the front of the array will override those at the back.
+    member __.Arbitrary with get() = arbitrary and set(v) = arbitrary <- v; config <- {config with Arbitrary = v}
+    ///If set, suppresses the output from the test if the test is successful. This can be useful when running tests
+    ///with TestDriven.net, because TestDriven.net pops up the Output window in Visual Studio if a test fails; thus,
+    ///when conditioned to that behaviour, it's always a bit jarring to receive output from passing tests.
+    ///The default is false, which means that FsCheck will also output test results on success, but if set to true,
+    ///FsCheck will suppress output in the case of a passing test. This setting doesn't affect the behaviour in case of
+    ///test failures.
+    member __.QuietOnSuccess with get() = quietOnSuccess and set(v) = quietOnSuccess <- v; config <- {config with QuietOnSuccess = Some v}
+
+    member internal __.Config = config
+and
+    ///Set common configuration for all properties within this class or module
+    [<AttributeUsage(AttributeTargets.Class ||| AttributeTargets.Assembly, AllowMultiple = false)>]
+    public PropertiesAttribute() = inherit PropertyAttribute()
+and
+/// The xUnit2 test runner for the PropertyAttribute that executes the test via FsCheck
+    PropertyTestCase(diagnosticMessageSink:IMessageSink, defaultMethodDisplay:TestMethodDisplay, defaultMethodDisplayOptions:TestMethodDisplayOptions, testMethod:ITestMethod, ?testMethodArguments:obj []) =
+    inherit XunitTestCase(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, (match testMethodArguments with | None -> null | Some v -> v))
+
+    let combineAttributes (configs: (PropertyConfig option) list) =
+        configs
+        |> List.choose id
+        |> List.reduce(fun higherLevelAttribute lowerLevelAttribute -> 
+            PropertyConfig.combine lowerLevelAttribute higherLevelAttribute)
+
+    new() = new PropertyTestCase(null, TestMethodDisplay.ClassAndMethod, TestMethodDisplayOptions.None, null)
+
+    member this.Init(output:TestOutputHelper) =
+        let getPropertiesOnDeclaringClasses (testClass: ITestClass) = 
+            [   let mutable current: Type = testClass.Class.ToRuntimeType()
+                while not (isNull current) do
+                    yield current.GetTypeInfo().GetCustomAttributes<PropertiesAttribute>()
+                          |> Seq.tryHead
+                          |> Option.map (fun attr -> attr.Config)
+                    current <- current.DeclaringType]
+            |> List.rev
+            
+        let getConfig (attr: IAttributeInfo) =
+            attr.GetNamedArgument<PropertyConfig> "Config"
+
+        let config = combineAttributes [
+              yield this.TestMethod.TestClass.Class.Assembly.GetCustomAttributes(typeof<PropertiesAttribute>) |> Seq.tryHead |> Option.map getConfig
+              yield! getPropertiesOnDeclaringClasses this.TestMethod.TestClass
+              yield this.TestMethod.Method.GetCustomAttributes(typeof<PropertyAttribute>) |> Seq.head |> getConfig |> Some]
+        
+        { config with Arbitrary = config.Arbitrary }
+        |> PropertyConfig.toConfig output 
+
+    override this.RunAsync(diagnosticMessageSink:IMessageSink, messageBus:IMessageBus, constructorArguments:obj [], aggregator:ExceptionAggregator, cancellationTokenSource:Threading.CancellationTokenSource) =
+        let test = XunitTest(this, this.DisplayName)
+        let summary = RunSummary(Total = 1);
+
+        // 1. We always need an initialized TestOutputHelper so we can write output.
+        // 2. xunit supports test classes that have a constructor that takes a TestOutputHelper, which we need to pass along.
+        // xunit has two different ways of passing us the TestOutputHelper.
+        // pre-2.9.0: as a TestOutputHelper constructor argument
+        // post-2.9.0: as a Func<TestOutputHelper> constructor argument https://github.com/xunit/xunit/issues/2996#issuecomment-2271764192
+        // The below code handles both cases.
+        let mutable outputHelper = TestOutputHelper()
+        for i in 0..constructorArguments.Length-1 do
+            match constructorArguments[i] with
+            | :? TestOutputHelper as foundHelper -> 
+                outputHelper <- foundHelper
+            | :? Func<TestOutputHelper> as foundHelperFunc -> 
+                outputHelper <- foundHelperFunc.Invoke()
+                constructorArguments.[i] <- outputHelper
+            | _ -> ()
+        outputHelper.Initialize(messageBus, test)
+
+        let dispose testClass =
+            match testClass with
+            | None -> ()
+            | Some obj ->
+                match box obj with
+                | :? IDisposable as d -> d.Dispose()
+                | _ -> ()
+
+        let testExec() =
+            let config = this.Init(outputHelper)
+            let timer = ExecutionTimer()
+            let result =
+                try
+                    let xunitRunner = if config.Runner :? XunitRunner then (config.Runner :?> XunitRunner) else XunitRunner()
+                    let runMethod = this.TestMethod.Method.ToRuntimeMethod()
+                    let target =
+                        let testClass = this.TestMethod.TestClass.Class.ToRuntimeType()
+                        if (not (isNull this.TestMethod.TestClass)) && not this.TestMethod.Method.IsStatic then
+
+                            let testInstance = test.CreateTestClass(testClass, constructorArguments, messageBus, timer, cancellationTokenSource)
+
+                            match testInstance with
+                            | :? IAsyncLifetime as asyncLifetime -> asyncLifetime.InitializeAsync()
+                            | _ -> Task.CompletedTask
+                            |> Async.AwaitTask
+                            |> Async.StartAsTask
+                            |> Task.WaitAll
+
+                            Some testInstance
+                        else None
+
+                    timer.Aggregate(fun () -> Check.Method(config, runMethod, ?target=target))
+
+                    dispose target
+
+                    match xunitRunner.Result with
+                          | TestResult.Passed _ ->
+                            let output = Runner.onFinishedToString "" xunitRunner.Result
+                            outputHelper.WriteLine(output)
+                            TestPassed(test, timer.Total, outputHelper.Output) :> TestResultMessage
+                          | TestResult.Exhausted _ ->
+                            summary.Failed <- summary.Failed + 1
+                            upcast TestFailed(test, timer.Total, outputHelper.Output, PropertyFailedException(xunitRunner.Result))
+                          | TestResult.Failed (testdata, originalArgs, shrunkArgs, Outcome.Failed e, originalSeed, lastSeed, lastSize)  ->
+                            summary.Failed <- summary.Failed + 1
+                            let message = sprintf "%s%s" Environment.NewLine (Runner.onFailureToString "" testdata originalArgs shrunkArgs originalSeed lastSeed lastSize)
+                            upcast TestFailed(test, timer.Total, outputHelper.Output, PropertyFailedException(message, e))
+                          | TestResult.Failed _ ->
+                            summary.Failed <- summary.Failed + 1
+                            upcast TestFailed(test, timer.Total, outputHelper.Output, PropertyFailedException(xunitRunner.Result))
+                with
+                    | ex ->
+                      summary.Failed <- summary.Failed + 1
+                      outputHelper.WriteLine("Exception during test")
+                      upcast TestFailed(test, timer.Total, outputHelper.Output, ex)
+
+
+            outputHelper.Uninitialize()
+
+            messageBus.QueueMessage(result) |> ignore
+            summary.Time <- summary.Time + result.ExecutionTime
+            if not (messageBus.QueueMessage(TestFinished(test, summary.Time, result.Output))) then
+                cancellationTokenSource.Cancel() |> ignore
+            if not (messageBus.QueueMessage(TestCaseFinished(this, summary.Time, summary.Total, summary.Failed, summary.Skipped))) then
+                cancellationTokenSource.Cancel() |> ignore
+
+            summary
+
+        if not (messageBus.QueueMessage(TestCaseStarting(this))) then
+            cancellationTokenSource.Cancel() |> ignore
+
+        if not (messageBus.QueueMessage(TestStarting(test))) then
+            cancellationTokenSource.Cancel() |> ignore
+
+        if not(String.IsNullOrEmpty(this.SkipReason)) then
+            summary.Skipped <- summary.Skipped + 1
+            if not(messageBus.QueueMessage(TestSkipped(test, this.SkipReason))) then
+                cancellationTokenSource.Cancel() |> ignore
+            if not(messageBus.QueueMessage(TestCaseFinished(this, decimal 1, 0, 0, 1))) then
+                cancellationTokenSource.Cancel() |> ignore
+            Task.FromResult(summary)
+        else
+            Task.Run(testExec)
+and
+/// xUnit2 test case discoverer to link the method with the PropertyAttribute to the PropertyTestCase
+/// so the test can be run via FsCheck.
+    PropertyDiscoverer(messageSink:IMessageSink) =
+
+    new () = PropertyDiscoverer(null)
+
+    member __.MessageSink = messageSink
+
+    interface IXunitTestCaseDiscoverer with
+        override this.Discover(discoveryOptions:ITestFrameworkDiscoveryOptions, testMethod:ITestMethod, _:IAttributeInfo)=
+            let ptc = new PropertyTestCase(this.MessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod)
+            Seq.singleton (ptc :> IXunitTestCase)
diff --git a/src/FsCheck.Xunit.v3/Runner.fs b/src/FsCheck.Xunit.v3/Runner.fs
new file mode 100644
index 00000000..a706017e
--- /dev/null
+++ b/src/FsCheck.Xunit.v3/Runner.fs
@@ -0,0 +1,21 @@
+namespace FsCheck.Xunit.v3
+
+open FsCheck
+open global.Xunit.v3
+   
+/// A runner for FsCheck (i.e. that you can use as Config.Runner) which outputs
+/// to Xunit's given ITestOutputHelper.
+/// For example, { Config.QuickThrowOnFailure with Runner = TestOutputRunner(output) }
+type TestOutputRunner(output: TestOutputHelper) =
+    interface IRunner with
+        member _.OnStartFixture t =
+            output.WriteLine (Runner.onStartFixtureToString t)
+        member _.OnArguments (ntest, args, every) =
+            output.WriteLine (every ntest args)
+        member _.OnShrink(args, everyShrink) =
+            output.WriteLine (everyShrink args)
+        member _.OnFinished(name,testResult) =
+            let resultText = Runner.onFinishedToString name testResult
+            match testResult with
+            | TestResult.Passed _ -> resultText |> output.WriteLine
+            | _ -> failwithf "%s" resultText
\ No newline at end of file