Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
name: Publish

on:
push:
branches:
# trigger only on completion of Build on main
workflow_run:
workflows: ["Build"]
branches:
- main
types:
- completed

jobs:
publish:
Expand All @@ -29,8 +33,7 @@ jobs:

- name: Run Versionize
id: versionize
run: |
dotnet versionize
run: dotnet versionize
continue-on-error: true

- name: No release required
Expand Down
12 changes: 6 additions & 6 deletions ApiStub.FSharp/BDD.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module BDD =
/// Defines a BDD scenario
type Scenario<'TStartup when 'TStartup: not struct> =
{ UseCase: string
TestClient: TestClient<'TStartup> }
TestWAFBuilder: TestWebAppFactoryBuilder<'TStartup> }

/// Defines the context propagated through the test
type Environment<'TStartup, 'FeatureStubData when 'TStartup: not struct> =
Expand Down Expand Up @@ -42,9 +42,9 @@ module BDD =


/// Scenario builder
let SCENARIO useCase testClient =
let SCENARIO useCase (builder: TestWebAppFactoryBuilder<_>) =
{ UseCase = useCase
TestClient = testClient }
TestWAFBuilder = builder }
|> Step.Scenario

/// Setup the Environment for the given scenario
Expand Down Expand Up @@ -152,17 +152,17 @@ module BDD =


// [<Fact>] sample
// let ``when i call /hello i get 'world' back with 200 ok`` (testClient: TestClient<_>) =
// let ``when i call /hello i get 'world' back with 200 ok`` (TestWebAppFactoryBuilder: TestWebAppFactoryBuilder<_>) =

// let stubData = [ 1, 2, 3 ]

// testClient { GET "/hello" (fun _ _ -> $"hello world {stubData}" |> R_TEXT) }
// TestWebAppFactoryBuilder { GET "/hello" (fun _ _ -> $"hello world {stubData}" |> R_TEXT) }
// |> SCENARIO "when i call /hello i get 'world' back with 200 ok"
// |> SETUP
// (fun s ->
// task {

// let test = s.TestClient
// let test = s.TestWebAppFactoryBuilder

// let f = test.GetFactory()

Expand Down
26 changes: 11 additions & 15 deletions ApiStub.FSharp/CE.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ open Microsoft.Extensions.Http
open Microsoft.AspNetCore.Routing.Template
open Microsoft.AspNetCore.Routing

/// computation expression module (builder CE), contains `TestClient<T>` that wraps `WebApplicationFactory<T>`
/// computation expression module (builder CE), contains `TestWebAppFactoryBuilder` (former `TestWebAppFactoryBuilder`) that wraps `WebApplicationFactory`
module CE =
open BuilderExtensions
open HttpResponseHelpers
Expand All @@ -18,8 +18,8 @@ module CE =
let private toAsync stub =
fun req args -> task { return stub req args }

/// `TestClient<T>` wraps `WebApplicationFactory<T>` and exposes a builder CE with utility to define api client stubs and other features
type TestClient<'T when 'T: not struct>() =
/// `TestWebAppFactoryBuilder` wraps `WebApplicationFactory` and exposes a builder CE with utility to define api client stubs and other features
type TestWebAppFactoryBuilder<'T when 'T: not struct>() =

let factory = new WebApplicationFactory<'T>()
let mutable httpMessageHandler: DelegatingHandler = null
Expand Down Expand Up @@ -82,22 +82,14 @@ module CE =

[<CustomOperation("stub")>]
member this.Stub
(
x,
methods,
routeTemplate,
stub: HttpRequestMessage -> RouteValueDictionary -> HttpResponseMessage
) =
(x, methods, routeTemplate, stub: HttpRequestMessage -> RouteValueDictionary -> HttpResponseMessage)
=
this.StubWithOptions(x, methods, routeTemplate, stub |> toAsync, false)

[<CustomOperation("stub_async")>]
member this.StubAsync
(
x,
methods,
routeTemplate,
stub: HttpRequestMessage -> RouteValueDictionary -> HttpResponseMessage Task
) =
(x, methods, routeTemplate, stub: HttpRequestMessage -> RouteValueDictionary -> HttpResponseMessage Task)
=
this.StubWithOptions(x, methods, routeTemplate, stub, false)

/// stub operation with stub object (HttpResponseMessage)
Expand Down Expand Up @@ -232,3 +224,7 @@ module CE =
|> web_configure_test_services (fun s ->
for custom_config in customConfigureTestServices do
custom_config (s) |> ignore)

/// `TestClient` is a type backfill for `TestWebAppFactoryBuilder`, please switch to the new name if possible
[<Obsolete("Use TestWebAppFactoryBuilder type instead")>]
type TestClient<'T when 'T: not struct> = TestWebAppFactoryBuilder<'T>
12 changes: 7 additions & 5 deletions ApiStub.FSharp/Csharp.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@ open ApiStub.FSharp.CE
type CsharpExtensions =

[<Extension>]
static member GETJ<'a when 'a: not struct>(x: TestClient<'a>, route: string, stub: obj) = x.GetJson(x, route, stub)
static member GETJ<'a when 'a: not struct>(x: TestWebAppFactoryBuilder<'a>, route: string, stub: obj) =
x.GetJson(x, route, stub)

[<Extension>]
static member POSTJ<'a when 'a: not struct>(x: TestClient<'a>, route: string, stub: obj) =
static member POSTJ<'a when 'a: not struct>(x: TestWebAppFactoryBuilder<'a>, route: string, stub: obj) =
x.PostJson(x, route, stub)

[<Extension>]
static member PUTJ<'a when 'a: not struct>(x: TestClient<'a>, route: string, stub: obj) = x.PutJson(x, route, stub)
static member PUTJ<'a when 'a: not struct>(x: TestWebAppFactoryBuilder<'a>, route: string, stub: obj) =
x.PutJson(x, route, stub)

[<Extension>]
static member DELETEJ<'a when 'a: not struct>(x: TestClient<'a>, route: string, stub: obj) =
static member DELETEJ<'a when 'a: not struct>(x: TestWebAppFactoryBuilder<'a>, route: string, stub: obj) =
x.DeleteJson(x, route, stub)

[<Extension>]
static member PATCHJ<'a when 'a: not struct>(x: TestClient<'a>, route: string, stub: obj) =
static member PATCHJ<'a when 'a: not struct>(x: TestWebAppFactoryBuilder<'a>, route: string, stub: obj) =
x.PatchJson(x, route, stub)
54 changes: 29 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ Access the [documentation website](https://jkone27.github.io/fsharp-integration-

```mermaid
sequenceDiagram
participant TestClient as Test
participant TestWebAppFactoryBuilder as Test
participant MainApp as App
participant DependencyApp as Dep

TestClient->>MainApp: GET /Hello
TestWebAppFactoryBuilder->>MainApp: GET /Hello
MainApp->>DependencyApp: GET /externalApi
DependencyApp-->>MainApp: Response
MainApp-->>TestClient: Response
MainApp-->>TestWebAppFactoryBuilder: Response

```

Expand All @@ -44,45 +44,49 @@ open Xunit
module Tests =

// build your aspnetcore integration testing CE
let test = new TestClient<Program>()
let test = new TestWebAppFactoryBuilder<Program>()

[<Fact>]
let ``Calls Hello and returns OK`` () =
let ``Calls Hello and returns OK`` () = task {

task {
let client =
test {
GETJ "/externalApi" {| Ok = "yeah" |}
}
|> _.GetFactory()
|> _.CreateClient()

let client =
test {
GETJ "/externalApi" {| Ok = "yeah" |}
}
|> _.GetFactory()
|> _.CreateClient()
let! r = client.GetAsync("/Hello")

let! r = client.GetAsync("/Hello")
// rest of your tests...

// rest of your tests...

}
}
```

or in `C#` if you prefer

```csharp
using ApiStub.FSharp;
using Xunit;
using static ApiStub.Fsharp.CsharpExtensions;

async Task CallsHelloAndReturnsOk () {

var client =
new CE.TestClient<Web.Sample.Program>()
.GETJ("/externalApi", new { Ok = "Yeah" })
.GetFactory()
.CreateClient();
public class Tests
{
[Fact]
async Task CallsHelloAndReturnsOk()
{

var r = await client.GetAsync("/Hello");
var client =
new CE.TestWebAppFactoryBuilder<Web.Sample.Program>()
.GETJ("/externalApi", new { Ok = "Yeah" })
.GetFactory()
.CreateClient();

// rest of your tests...
var r = await client.GetAsync("/Hello");

// rest of your tests...
}
}
```

### Test .NET C# 🤝 from F#
Expand Down
Loading