-
Notifications
You must be signed in to change notification settings - Fork 0
Add comprehensive testing infrastructure with xUnit, Playwright, and WebApplicationFactory #74
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
ce1255d
cfa32bc
b757d8a
c0801d6
165b325
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 | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,258 @@ | ||||||||||||||||||
| using System.Net.Http.Json; | ||||||||||||||||||
| using System.Text; | ||||||||||||||||||
| using System.Text.Json; | ||||||||||||||||||
| using FluentAssertions; | ||||||||||||||||||
| using PhantomDave.BankTracking.IntegrationTests.Helpers; | ||||||||||||||||||
|
|
||||||||||||||||||
| namespace PhantomDave.BankTracking.IntegrationTests.GraphQL; | ||||||||||||||||||
|
|
||||||||||||||||||
| public class AccountIntegrationTests : IClassFixture<GraphQLTestFactory> | ||||||||||||||||||
| { | ||||||||||||||||||
| private readonly HttpClient _client; | ||||||||||||||||||
| private readonly GraphQLTestFactory _factory; | ||||||||||||||||||
|
|
||||||||||||||||||
| public AccountIntegrationTests(GraphQLTestFactory factory) | ||||||||||||||||||
| { | ||||||||||||||||||
| _factory = factory; | ||||||||||||||||||
| _client = factory.CreateClient(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| [Fact] | ||||||||||||||||||
| public async Task GraphQL_Endpoint_IsAccessible() | ||||||||||||||||||
| { | ||||||||||||||||||
| // Arrange & Act | ||||||||||||||||||
| var response = await _client.GetAsync("/graphql?sdl"); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Assert | ||||||||||||||||||
| response.EnsureSuccessStatusCode(); | ||||||||||||||||||
| var content = await response.Content.ReadAsStringAsync(); | ||||||||||||||||||
| content.Should().Contain("type Query"); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| [Fact] | ||||||||||||||||||
| public async Task CreateAccount_WithValidData_ReturnsSuccess() | ||||||||||||||||||
| { | ||||||||||||||||||
| // Arrange | ||||||||||||||||||
| var query = @" | ||||||||||||||||||
| mutation { | ||||||||||||||||||
| createAccount(email: ""test@example.com"", password: ""Password123!"") { | ||||||||||||||||||
| id | ||||||||||||||||||
| createdAt | ||||||||||||||||||
| } | ||||||||||||||||||
| }"; | ||||||||||||||||||
|
Comment on lines
+36
to
+43
|
||||||||||||||||||
|
|
||||||||||||||||||
| var request = new | ||||||||||||||||||
| { | ||||||||||||||||||
| query = query | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| // Act | ||||||||||||||||||
| var response = await _client.PostAsJsonAsync("/graphql", request); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Assert | ||||||||||||||||||
| response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); | ||||||||||||||||||
| var content = await response.Content.ReadAsStringAsync(); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (content.Contains("errors")) | ||||||||||||||||||
| { | ||||||||||||||||||
| content.Should().Contain("test@example.com", "Even with errors, successful account creation should return email"); | ||||||||||||||||||
| } | ||||||||||||||||||
| else | ||||||||||||||||||
| { | ||||||||||||||||||
| content.Should().Contain("test@example.com"); | ||||||||||||||||||
| content.Should().Contain("\"id\""); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| [Fact] | ||||||||||||||||||
| public async Task CreateAccount_WithDuplicateEmail_ReturnsError() | ||||||||||||||||||
| { | ||||||||||||||||||
| // Arrange | ||||||||||||||||||
| var createQuery = @" | ||||||||||||||||||
| mutation { | ||||||||||||||||||
| createAccount(email: ""duplicate@example.com"", password: ""Password123!"") { | ||||||||||||||||||
| id | ||||||||||||||||||
| } | ||||||||||||||||||
| }"; | ||||||||||||||||||
|
|
||||||||||||||||||
| await _client.PostAsJsonAsync("/graphql", new { query = createQuery }); | ||||||||||||||||||
|
||||||||||||||||||
| await _client.PostAsJsonAsync("/graphql", new { query = createQuery }); | |
| var firstResponse = await _client.PostAsJsonAsync("/graphql", new { query = createQuery }); | |
| firstResponse.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); | |
| var firstContent = await firstResponse.Content.ReadAsStringAsync(); | |
| firstContent.Should().NotContain("errors", "First account creation should succeed to properly test duplicate scenario"); |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The account creation result is not checked before attempting login. If account creation fails, the login test will also fail but for the wrong reason, making debugging harder. Consider verifying the creation response before proceeding.
| await _client.PostAsJsonAsync("/graphql", new { query = createQuery }); | |
| var createResponse = await _client.PostAsJsonAsync("/graphql", new { query = createQuery }); | |
| var createContent = await createResponse.Content.ReadAsStringAsync(); | |
| createResponse.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); | |
| createContent.Should().NotContain("\"errors\"", "Account creation should succeed before attempting login"); | |
| createContent.Should().Contain("\"id\"", "Account creation response should contain an id"); |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential NullReferenceException if the JSON parsing fails or the expected properties don't exist in the response. The code attempts to access nested properties without null checking. Add proper error handling or use TryGetProperty to safely navigate the JSON structure, especially since some tests acknowledge that GraphQL schema issues may cause unexpected responses.
| var token = doc.RootElement.GetProperty("data").GetProperty("loginAccount").GetProperty("token").GetString(); | |
| if (!doc.RootElement.TryGetProperty("data", out var dataElem)) | |
| throw new Xunit.Sdk.XunitException("Response JSON does not contain 'data' property: " + loginContent); | |
| if (!dataElem.TryGetProperty("loginAccount", out var loginAccountElem)) | |
| throw new Xunit.Sdk.XunitException("Response JSON does not contain 'loginAccount' property: " + loginContent); | |
| if (!loginAccountElem.TryGetProperty("token", out var tokenElem)) | |
| throw new Xunit.Sdk.XunitException("Response JSON does not contain 'token' property: " + loginContent); | |
| var token = tokenElem.GetString(); |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as lines 185-186: potential NullReferenceException when parsing the JSON response without null checking. The nested property access could fail if the response structure is unexpected.
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||
| using Microsoft.AspNetCore.Hosting; | ||||
| using Microsoft.AspNetCore.Mvc.Testing; | ||||
| using Microsoft.AspNetCore.TestHost; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.Extensions.Configuration; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.DependencyInjection.Extensions; | ||||
| using PhantomDave.BankTracking.Data.Context; | ||||
|
|
||||
| namespace PhantomDave.BankTracking.IntegrationTests.Helpers; | ||||
|
|
||||
| public class GraphQLTestFactory : WebApplicationFactory<PhantomDave.BankTracking.Api.Program> | ||||
| { | ||||
| protected override void ConfigureWebHost(IWebHostBuilder builder) | ||||
| { | ||||
| builder.ConfigureAppConfiguration((context, config) => | ||||
| { | ||||
| config.AddInMemoryCollection(new Dictionary<string, string?> | ||||
| { | ||||
| ["ConnectionStrings:DefaultConnection"] = "Host=localhost;Database=banktrackertest;Username=test;Password=test", | ||||
|
||||
| ["ConnectionStrings:DefaultConnection"] = "Host=localhost;Database=banktrackertest;Username=test;Password=test", |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The in-memory database name "InMemoryTestDb" is shared across all tests. Since xUnit runs tests in parallel by default, this could lead to race conditions where tests interfere with each other's data. Consider using a unique database name per test instance (e.g., using a GUID) to ensure proper test isolation:
options.UseInMemoryDatabase($"InMemoryTestDb_{Guid.NewGuid()}");| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| <IsPackable>false</IsPackable> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="coverlet.collector" /> | ||
| <PackageReference Include="Microsoft.NET.Test.Sdk" /> | ||
| <PackageReference Include="xunit" /> | ||
| <PackageReference Include="xunit.runner.visualstudio" /> | ||
| <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" /> | ||
| <PackageReference Include="FluentAssertions" /> | ||
| <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <Using Include="Xunit" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\PhantomDave.BankTracking.Api\PhantomDave.BankTracking.Api.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
_factoryfield is stored but never used in any test methods. This field can be removed since only_clientis used throughout the test class.