@@ 5233,6 5233,77 @@ let factory = serviceProvider.GetService<IHttpClientFactory>();
let client = factory.CreateClient()
#+end_src
+** Testing ASP.NET APIs with Expecto :dotnet:fsharp:
+:PROPERTIES:
+:EXPORT_DATE: 2023-07-10
+:EXPORT_FILE_NAME: testing-asp-dot-net-apis-with-expecto
+:EXPORT_HUGO_SLUG: testing-asp-dot-net-apis-with-expecto
+:END:
+
+#+begin_description
+On how to concurrently test ASP.NET APIs with Expecto.
+#+end_description
+
+It has been a while since I last wrote down something here. However, I
+believe this to be one of the most useful "/tricks/" I have learned
+the past few weeks.
+
+Running "end to end integration tests" on endpoints in F# have always
+been a struggle for me. While running tests, I want to keep the
+application both stateless and running the whole test suite
+concurrently.
+
+Fortunately, [[https://github.com/haf/expecto][Expecto]] provides an easy way to add fixtures to your
+tests. This means that we can leverage this feature to start a new web
+server for each test and guarantee that we always have a clean state
+at the beginning of a test.
+
+Before starting it, let's assume some things on our code:
+
+- There's a ~createHost~ function that receives a port number, a [[https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.host.createdefaultbuilder?view=dotnet-plat-ext-7.0][Host
+ builder]] and returns a "built builder".
+- We just need an [[https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.httpclient?view=net-7.0][HttpClient]] pointing to our web api on our tests.
+
+#+begin_src fsharp
+let withApi test = task {
+ let builder = Host.CreateDefaultBuilder()
+
+ // passing a ~zero~ port to our app makes ASP.NET assign a random
+ // port when initializing the service, this avoids conflicts while
+ // running tests in parallel.
+ let hostPort = 0
+ let host = createHost hostPort builder
+
+ let server = host.Services.GetRequiredService<IServer>()
+ let addressFeature = server.Features.Get<IServerAddressesFeature>()
+
+ do! host.StartAsync()
+
+ let port =
+ let appUri = addressFeature.Addresses |> Seq.head |> Uri
+ appUri.Port
+
+ let client = new HttpClient()
+ client.BaseAddress <- Uri(sprintf "http://localhost:%d" port)
+
+ do! test client
+ do! host.StopAsync()
+}
+#+end_src
+
+Consuming this fixture is rather easy:
+
+#+begin_src fsharp
+testList "api tests" [
+ yield! testFixtureTask withApi [
+ "get user", fun client -> task {
+ let! user = client.GetAsync("/user/admin")
+ // ...
+ }
+ ]
+]
+#+end_src
+
* Footnotes
[fn:45] Fun fact: I introduced the dotnet module on devenv!