-
-
Notifications
You must be signed in to change notification settings - Fork 138
fix(aspnetcore): honor WebApplicationFactoryClientOptions in CreateClient #6931
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ba79eab
7483db4
a4043ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -233,14 +233,20 @@ private static ServiceDescriptor WrapHostedServiceDescriptor(ServiceDescriptor d | |
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates an <see cref="HttpClient"/> with automatic Activity tracing and test context propagation. | ||
| /// Equivalent to calling <see cref="CreateDefaultClient(DelegatingHandler[])"/> with no additional handlers. | ||
| /// Creates an <see cref="HttpClient"/> configured by <see cref="WebApplicationFactory{TEntryPoint}.ClientOptions"/>, | ||
| /// with automatic Activity tracing and test context propagation. | ||
| /// </summary> | ||
| public new HttpClient CreateClient() | ||
| { | ||
| var client = CreateDefaultClient(); | ||
| ConfigureClient(client); | ||
| return client; | ||
| } | ||
| public new HttpClient CreateClient() => CreateClient(ClientOptions); | ||
|
|
||
| /// <summary> | ||
| /// Creates an <see cref="HttpClient"/> configured by <paramref name="options"/>, with automatic | ||
| /// Activity tracing and test context propagation. Honors | ||
| /// <see cref="WebApplicationFactoryClientOptions.AllowAutoRedirect"/>, | ||
| /// <see cref="WebApplicationFactoryClientOptions.MaxAutomaticRedirections"/>, | ||
| /// <see cref="WebApplicationFactoryClientOptions.HandleCookies"/> and | ||
| /// <see cref="WebApplicationFactoryClientOptions.BaseAddress"/>. | ||
| /// </summary> | ||
| public new HttpClient CreateClient(WebApplicationFactoryClientOptions options) => | ||
| TUnitHttpClientFilter.CreateClient(base.CreateDefaultClient, options); | ||
|
Comment on lines
+249
to
+250
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This overload and the matching AGENTS.md reference: AGENTS.md:L15-L16 Useful? React with 👍 / 👎. |
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| using System.Net; | ||
| using Microsoft.AspNetCore.Mvc.Testing; | ||
| using TUnit.AspNetCore; | ||
| using TUnit.Core; | ||
|
|
||
| namespace TUnit.AspNetCore.Tests; | ||
|
|
||
| /// <summary> | ||
| /// Regression coverage for thomhurst/TUnit#6921: clients created by | ||
| /// <see cref="TestWebApplicationFactory{TEntryPoint}"/> must honor | ||
| /// <see cref="WebApplicationFactoryClientOptions"/> (cookies, redirects, base address) | ||
| /// while still carrying TUnit's propagation headers. | ||
| /// </summary> | ||
| public class ClientOptionsTests | ||
| { | ||
| [ClassDataSource(Shared = [SharedType.PerTestSession])] | ||
| public TestWebAppFactory Factory { get; set; } = null!; | ||
|
|
||
| [Test] | ||
| public async Task CreateClient_HandlesCookies_ByDefault() | ||
| { | ||
| using var client = Factory.CreateClient(); | ||
|
|
||
| await AssertCookieRoundTrip(client, expected: "abc"); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CreateClient_FollowsRedirects_ByDefault() | ||
| { | ||
| using var client = Factory.CreateClient(); | ||
|
|
||
| var response = await client.GetAsync("/redirect"); | ||
|
|
||
| await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK); | ||
| await Assert.That(await response.Content.ReadAsStringAsync()).IsEqualTo("pong"); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CreateClient_WithOptions_CanDisableCookiesAndRedirects() | ||
| { | ||
| using var client = Factory.CreateClient(new WebApplicationFactoryClientOptions | ||
| { | ||
| HandleCookies = false, | ||
| AllowAutoRedirect = false, | ||
| }); | ||
|
|
||
| await AssertCookieRoundTrip(client, expected: "<none>"); | ||
|
|
||
| var response = await client.GetAsync("/redirect"); | ||
| await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.Redirect); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CreateClient_WithOptions_UsesBaseAddress() | ||
| { | ||
| var baseAddress = new Uri("http://tunit.test/"); | ||
|
|
||
| using var client = Factory.CreateClient(new WebApplicationFactoryClientOptions { BaseAddress = baseAddress }); | ||
|
|
||
| await Assert.That(client.BaseAddress).IsEqualTo(baseAddress); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CreateClient_WithOptions_KeepsPropagationHeaders() | ||
| { | ||
| using var client = Factory.CreateClient(new WebApplicationFactoryClientOptions()); | ||
|
|
||
| var echoed = await client.GetStringAsync("/echo-headers"); | ||
|
|
||
| await Assert.That(echoed).Contains(TUnitTestIdHandler.HeaderName + ": " + TestContext.Current!.Id); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CreateClient_Redirect_KeepsPropagationHeadersOnRedirectedRequest() | ||
| { | ||
| using var client = Factory.CreateClient(); | ||
|
|
||
| await AssertRedirectedRequestHasTestIdHeader(client); | ||
| } | ||
|
|
||
| internal static async Task AssertRedirectedRequestHasTestIdHeader(HttpClient client) | ||
| { | ||
| var response = await client.GetAsync("/redirect-to-echo-headers"); | ||
| await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK); | ||
|
|
||
| var lines = (await response.Content.ReadAsStringAsync()).Split('\n'); | ||
|
|
||
| // Exact line match proves a single value: /echo-headers joins repeated values with | ||
| // commas, so a header duplicated across redirect hops would not match this line. | ||
| await Assert.That(lines).Contains(TUnitTestIdHandler.HeaderName + ": " + TestContext.Current!.Id); | ||
| } | ||
|
|
||
| internal static async Task AssertCookieRoundTrip(HttpClient client, string expected) | ||
| { | ||
| var set = await client.GetAsync("/cookie/set/abc"); | ||
| set.EnsureSuccessStatusCode(); | ||
|
|
||
| var value = await client.GetStringAsync("/cookie/get"); | ||
|
|
||
| await Assert.That(value).IsEqualTo(expected); | ||
| } | ||
| } | ||
|
|
||
| public class WebApplicationTestClientOptionsTests : WebApplicationTest<TestWebAppFactory, Program> | ||
| { | ||
| [Test] | ||
| public async Task CreateClient_HandlesCookies_ByDefault() | ||
| { | ||
| using var client = Factory.CreateClient(); | ||
|
|
||
| await ClientOptionsTests.AssertCookieRoundTrip(client, expected: "abc"); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CreateClient_FollowsRedirects_ByDefault() | ||
| { | ||
| using var client = Factory.CreateClient(); | ||
|
|
||
| var response = await client.GetAsync("/redirect"); | ||
|
|
||
| await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK); | ||
| await Assert.That(await response.Content.ReadAsStringAsync()).IsEqualTo("pong"); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CreateClient_Redirect_KeepsPropagationHeadersOnRedirectedRequest() | ||
| { | ||
| using var client = Factory.CreateClient(); | ||
|
|
||
| await ClientOptionsTests.AssertRedirectedRequestHasTestIdHeader(client); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CreateClient_WithOptions_CanDisableCookiesAndRedirects() | ||
| { | ||
| using var client = Factory.CreateClient(new WebApplicationFactoryClientOptions | ||
| { | ||
| HandleCookies = false, | ||
| AllowAutoRedirect = false, | ||
| }); | ||
|
|
||
| await ClientOptionsTests.AssertCookieRoundTrip(client, expected: "<none>"); | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
|
|
||
| var response = await client.GetAsync("/redirect"); | ||
| await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.Redirect); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CreateClient_WithOptions_UsesBaseAddress() | ||
| { | ||
| var baseAddress = new Uri("http://tunit.test/"); | ||
|
|
||
| using var client = Factory.CreateClient(new WebApplicationFactoryClientOptions { BaseAddress = baseAddress }); | ||
|
|
||
| await Assert.That(client.BaseAddress).IsEqualTo(baseAddress); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task CreateClient_WithOptions_KeepsPropagationHeaders() | ||
| { | ||
| using var client = Factory.CreateClient(new WebApplicationFactoryClientOptions()); | ||
|
|
||
| var echoed = await client.GetStringAsync("/echo-headers"); | ||
|
|
||
| await Assert.That(echoed).Contains(TUnitTestIdHandler.HeaderName + ": " + TestContext.Current!.Id); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// A <c>ConfigureClient</c> override must still apply to clients created through the | ||
| /// option-aware <c>CreateClient</c> overloads. | ||
| /// </summary> | ||
| public class ConfigureClientOverrideTests | ||
| { | ||
| [ClassDataSource(Shared = [SharedType.PerTestSession])] | ||
| public ConfigureClientWebAppFactory Factory { get; set; } = null!; | ||
|
|
||
| [Test] | ||
| public async Task CreateClient_RunsConfigureClientOverride() | ||
| { | ||
| using var client = Factory.CreateClient(); | ||
|
|
||
| var echoed = await client.GetStringAsync("/echo-headers"); | ||
|
|
||
| await Assert.That(echoed).Contains(ConfigureClientWebAppFactory.HeaderName + ": yes"); | ||
| } | ||
| } | ||
|
|
||
| public class ConfigureClientWebAppFactory : TestWebAppFactory | ||
| { | ||
| public const string HeaderName = "X-Configured-By-Override"; | ||
|
|
||
| protected override void ConfigureClient(HttpClient client) | ||
| { | ||
| base.ConfigureClient(client); | ||
| client.DefaultRequestHeaders.Add(HeaderName, "yes"); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.