I'm trying to build some integration tests for a small API I've built using the Saturn framework.
The API is built with the usual Saturn computation expressions such as application, controller, router etc.
But in order to build integration tests I need to replace the application computation expression (ce) and hand craft a WebHostBuilder.
My application ce looks like this:
module Server
let app =
application {
url ("http://0.0.0.0:" + port.ToString() + "/")
use_router apiRouter
memory_cache
service_config configureSerialization
use_gzip
use_config (fun _ ->
System.Environment.CurrentDirectory <- (System.Reflection.Assembly.GetExecutingAssembly()).Location
|> Path.GetDirectoryName
let configurationRoot =
ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appSettings.json")
.Build()
let appConfig = FsConfig.AppConfig(configurationRoot)
let dbPath =
match appConfig.Get<AppSettings>() with
| Ok settings when settings.DbConfig.Database.Contains(":memory:") -> settings.DbConfig.Database
| Ok settings -> Path.Combine(System.Environment.CurrentDirectory, settings.DbConfig.Database)
| Error _ -> failwithf "Invalid database path"
{ connectionString = dbPath |> sprintf "DataSource=%s;Version=3" })
}
With a router and on one of the controllers...
let cartController =
controller {
create createCartAction
show getCartAction
delete deleteCartAction
subController "/items" cartItemsController
}
let apiRouter =
router {
not_found_handler (setStatusCode 404 >=> text "Not found")
pipe_through apiPipeline
forward "/cart" cartController
}
The code above is in my API project, the code with integration test below is in a second project. The latter has a project reference to the former.
let configureApp (app : IApplicationBuilder) =
app.UseGiraffe(Server.apiRouter) \\<-- The problem is here. Server.apiRouter is null!
let configureServices (services : IServiceCollection) =
services.AddGiraffe() |> ignore
let builder = WebHostBuilder()
.UseContentRoot(contentRoot)
.Configure(Action<IApplicationBuilder> configureApp)
.ConfigureServices(configureServices)
let testServer = new TestServer(builder)
let client = testServer.CreateClient()
let! response = client.GetAsync "/"
test <@ HttpStatusCode.OK = response.StatusCode @>
When the test is run it fails with the following exception:
System.InvalidOperationException' occurred in System.Private.CoreLib.dll but was not handled in user code: 'A suitable constructor for type 'Giraffe.Middleware+GiraffeMiddleware' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.'
The problem appears to be with the line app.UseGiraffe(Server.apiRouter). apiRouter is defined in the Server module of the API project - but when this code runs in the test project Server.apiRouter is null.
If however I move the test code into the same project as the API - the test works perfectly.
Why would the apiRouter computation expression be null if called from the test project?
It turned out that this was nothing to do with
Saturnat all. It's all to do with the way thatF#initiatesmodules. The answer can be found hereAs suggested in the referenced post. I simply added an
[<EntryPoint>]function and everything worked as expected.