Skip to content

WebApplicationFactory<Program> can only be used with workaround in F# aspnet integration tests #18302

Description

@jkone27

the only way to make use of integration tests for aspnetcore from F# code , is either use the old Startup class setup (not valid for minimal api), or use this setup which makes use of a "trick" of declaring both a type and a module right after for Program as else the Program static class is not visible for unit/integration tests in the minimal api form?

Image

then it can be used by WebApplicationFactory<Program> in F# unit tests

Activity

  1. added this to the Backlog milestone on Feb 9, 2025
  2. jkone27 commented on Feb 9, 2025

    @jkone27
    ContributorAuthor

    closing as i need to verify something else, sorry

  3. jkone27 commented on Feb 9, 2025

    @jkone27
    ContributorAuthor

    ok checked and this also doesnt work it seems or at least can be confusing for new users...

    <ItemGroup>
         <InternalsVisibleTo Include="MyTestProject" />
    </ItemGroup>
    

    in F#, so probably we can reopen this one?

  4. reopened this on Feb 9, 2025
  5. brianrourkeboll commented on Feb 9, 2025

    @brianrourkeboll
    Contributor

    Yeah, it's because WebApplicationFactory requires that Program not be a static class, but the implicit or explicit Program module in F# is compiled as a static class.

    Another way you can work around it—which I think is conceptually simpler anyway—is to simply expose a public higher-order app function that accepts a builder configuration function and returns the built web app.

    In Program.fs, just call the app function and pass in ignore (or (fun _ -> ())), then call .Run () on the web app just like normal:

    Program.fs

    module Program
    
    open System
    open Microsoft.AspNetCore.Builder
    open Microsoft.Extensions.Hosting
    
    let app configureBuilder =
        let builder = WebApplication.CreateBuilder (Environment.GetCommandLineArgs ())
    
        // Your normal setup logic goes here, before the call to configureBuilder.
        ignore <| builder.Logging.AddConsole ()
        ignore <| builder.Services.AddSingleton<SomeService1> ()
        ignore <| builder.Services.AddSingleton<SomeService2> ()
        // …etc.
    
        ignore <| configureBuilder builder
    
        let app = builder.Build ()
        ignore <| app.MapGet ("/", Func<_> (fun () -> "Hello, world"))
        app
    
    (app ignore).Run ()

    In your tests, you can just pass in a function that configures the builder however you want, overriding services (or not), etc., as needed:

    Tests.fs

    let myTest route x y z =
        task {
            use app = Program.app (fun builder ->
                ignore <| builder.WebHost.UseTestServer ()
                // ignore <| builder.Services. … whatever else you want to override before `builder.Build ()` is called.
             )
    
            do! app.StartAsync ()
            use client = app.GetTestClient ()
        
            // The rest of your test logic.
            use! resp = client.GetAsync route
            // Your assertions, etc…
        }

    Here's another example.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions