Skip to content

Use WebApplicationFactory to run integration test hosts #563

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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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