Skip to content

Use WebApplicationFactory to run integration test hosts #563

@xperiandri

Description

@xperiandri

Curently F# FAKE build project from FSharp.Data.GraphQL.Build.slnx starts both FSharp.Data.GraphQL.IntegrationTests.Server.fsproj and star-wars-api.fsproj and executes tests against the real running server.
WebApplicationFactory from Microsoft.AspNetCore.Mvc.Testing NuGet package allows to simplify this by starting a host in tests process.

type IntegrationTestApplicationFactoryBase = WebApplicationFactory<FSharp.Data.GraphQL.IntegrationTests.Server.Program>

type IntegrationTestApplicationFactory () =
    inherit IntegrationTestApplicationFactoryBase ()

    [<Literal>]
    static let graphQLEndpoint = "/GraphQL"
    static member GraphQLEndpoint = graphQLEndpoint

    member factory.ConfigureHttpClientForTenant (httpClient : HttpClient) =
        httpClient.BaseAddress <- Uri (factory.Server.BaseAddress, TestApplicationFactory.GraphQLEndpoint)

    member private factory.ConfigureHttpClientForUser (userId : UserId) (httpClient : HttpClient) =
        httpClient.DefaultRequestHeaders.Authorization <- AuthenticationHeaderValue ("Bearer", $"{userIdString}")
        httpClient.BaseAddress <- Uri (factory.Server.BaseAddress, TestApplicationFactory.GraphQLEndpoint)

    member factory.CreateHttpClient ([<Optional>] userId) =
        let httpClient = factory.CreateClient ()
        factory.ConfigureConsoleHttpClientForUser userId httpClient
        httpClient

    override factory.ConfigureWebHost builder =

        builder.UseEnvironment Environments.Test |> ignore
        builder.ConfigureServices (fun services ->
            // TODO: Replace services with mocks here
            services
            |> ignore

            // Prevent background service failures from terminating the test host.
            // The main app's background services may fail (e.g. due to schema differences
            // in the local dev database), but tests don't depend on them.
            services.Configure<HostOptions> (fun (options : HostOptions) ->
                options.BackgroundServiceExceptionBehavior <- BackgroundServiceExceptionBehavior.Ignore
            )
            |> ignore
        )
        |> ignore
        base.ConfigureWebHost builder
type StarWarsTestApplicationFactoryBase = WebApplicationFactory<FSharp.Data.GraphQL.Samples.StarWarsApi.Program.Program>
[<AbstractClass; TestClass>]
type TestBase () =

    member val TestContext = Unchecked.defaultof<TestContext> with get, set

    member test.CancellationToken = test.TestContext.CancellationTokenSource.Token

[<AbstractClass; TestClass>]
type SingleHostIntegrationTestBase () =
    inherit TestBase ()

    [<DefaultValue>]
    static val mutable private application : TestApplicationFactoryBase

    static member Application = SingleHostIntegrationTestBase.application

    [<ClassInitialize(InheritanceBehavior.BeforeEachDerivedClass)>]
    static member Initialize (ctx : TestContext) = SingleHostIntegrationTestBase.application <- new TestApplicationFactory (ctx.FullyQualifiedTestClassName)

    [<ClassCleanup(InheritanceBehavior.BeforeEachDerivedClass)>]
    static member Cleanup () : Task = task { do! SingleHostIntegrationTestBase.application.DisposeAsync () }

[<AbstractClass; TestClass; TestCategory (TestCategories.Marten)>]
type IntegrationTestBase ()
    =
    inherit TestBase ()

    [<DefaultValue false>]
    val mutable private application : DatabaseTestApplicationFactory

    member test.Application = test.application

    abstract ConfigureServices : IServiceCollection -> unit
    default _.ConfigureServices _ = ()

    [<TestInitialize>]
    member test.Initialize () : Task = task {
        test.application <- 'Factory.Create (test.TestContext, test.ConfigureServices)
        do! test.application.InitializeAsync ()
    }

    [<TestCleanup>]
    member test.Cleanup () : Task = task {
        match withNull test.application with
        | null -> ()
        | application ->
            // The second test fails for some reason
            //do! test.application.DisposeAsync ()
            Task.Run (
                ``function`` =
                    Func<Task> (fun () -> task {
                        do! Task.Delay 1000
                        do! test.application.DisposeAsync ()
                    })
            )
            |> ignore
            test.application <- Unchecked.defaultof<_>
    }

Then tests can create type provider context if needed

open System
open FSharp.Data.GraphQL

//type IntegrationTestsClient = GraphQLProvider<"../../introspection.json", explicitOptionalParameters = true>

type WebApplicationFactory<_> with

    member factory.CreateClientContext (userId) =
        let httpClient = factory.CreateHttpClient (userId)
        let connection = new GraphQLClientConnection (httpClient)
        { ServerUrl = String.Empty; HttpHeaders = Seq.empty; Connection = connection }

type IntegrationTestBase with

    member test.CreateClientContext (userId) = test.Application.CreateClientContext (userId)

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions