JZuerlein/StockPriceTracker
GitHub: JZuerlein/StockPriceTracker
一个以高质量集成测试架构为核心的 ASP.NET Core minimal API 示例项目,展示了如何在同一套测试逻辑下对多数据库 provider 进行真实、可复用的自动化测试。
Stars: 2 | Forks: 1
# StockPriceTracker
一个小巧的 ASP.NET Core (.NET 10) minimal-API 演示项目,用于在身份验证下追踪股票价格。应用程序本身刻意保持精简,是作为演示“凭感觉编码 (vibe coded)”出来的——但**本仓库的重点在于其集成测试套件**。这些测试是经过深思熟虑编写的,旨在作为针对真实基础设施测试 minimal-API 应用程序的可重用模型。如果你只想学一件事,请阅读 [`StockPriceTracker.Tests.Integration`](StockPriceTracker.Tests.Integration)。
` 在内存中启动**真实的应用程序**,并通过 HTTP 进行驱动。这里有几个模式非常值得引入到你自己的项目中。
### 1. 一套测试逻辑,针对每个数据库 provider 运行
测试逻辑在一个泛型基类中只编写**一次**,然后通过为每个 provider 声明一个轻量级的具体子类,针对每个数据库 provider 执行:
```
public abstract class AddStockTestsBase : WebAppTestBase
where TFixture : WebAppFixtureBase
{
[Fact]
public async Task AddStock_WithJwtAuth_CreatesANewStock_WhenDataIsValid() { /* ... */ }
}
// Same tests, two real databases — zero duplicated test code:
public class AddStockWithSqliteTests : AddStockTestsBase, IClassFixture { }
public class AddStockWithPostgreSqlTests : AddStockTestsBase, IClassFixture { }
```
通过相同的断言,你既能获得开发期间 **SQLite** 的速度,**又**能拥有真实 **PostgreSQL** 服务器的保真度。只需添加一个 fixture 和一个单行子类即可添加新的 provider。
### 2. 共享 PostgreSQL 容器,每个 fixture 使用隔离的数据库
`PostgreSqlFixture` 为整个测试运行启动**一个** Testcontainers PostgreSQL 容器(只启动一次,延迟加载),然后在该容器内为每个 fixture 分配其自己唯一命名的数据库:
```
private static readonly PostgreSqlContainer SharedContainer = new PostgreSqlBuilder().Build();
private static readonly Lazy ContainerStart = new(() => SharedContainer.StartAsync());
private readonly string _databaseName = $"StockPriceTracker_{Guid.NewGuid():N}";
```
这是最佳平衡点:只需支付一次容器启动成本,同时保持测试类之间的数据彼此隔离。`SqliteFixture` 以相同的契约镜像了此过程,为每个 fixture 使用一个一次性的 `.sqlite` 文件,因此这两个 provider 是可以互换的。
### 3. 流畅的已验证客户端构建器(支持 JWT *和* Cookie 认证)
测试永远不会去处理真实的密码或 token。一个测试认证 handler 被替换进了 DI 容器中,并在流畅构建器的背后根据你的需求发出所需的 claims:
```
var client = CreateClient()
.WithJwtAuth(claims => claims.AsAdmin()) // or .WithCookieAuth(...)
.Build();
```
- `WithJwtAuth` / `WithCookieAuth` 选择认证方案,并将真实的 handler 替换为 `ConfigurableTestAuthHandler`。
- `ClaimsBuilder`(`AsAdmin()`, `AsUser(id)`, `WithRole(...)`, `WithClaim(...)`)使得测试中的身份标识明确且易于阅读。
- 由于该 handler 直接注入 claims,你可以无需建立登录流程即可测试**授权**(角色、策略)。
### 4. 一流的 CSRF / antiforgery 测试
基于 Cookie 认证的测试会执行真实的 antiforgery pipeline。构建的客户端已启用 Cookie 处理,并且扩展方法让 CSRF 的配合操作只需一行代码——甚至包括*负面*测试用例:
```
await client.WithCsrfTokenAsync(); // fetch + attach the token like a browser would
client.WithoutCsrfToken(); // prove protected calls are rejected without it
```
### 5. 精心排序且自我检查的 fixture 生命周期
`WebAppFixtureBase.InitializeAsync` 将其启动过程明确划分为四个有序、命名的阶段,然后**断言其自身的后置条件**,这样未来如果某次重构意外导致 host 的构建变成了延迟加载,它会在 setup 阶段就响亮地报错,而不是在后续神秘地失败:
```
await StartDatabaseAsync(); // 1. DB is up …
BuildFactory(); // 2. … before the host reads its connection string
var host = MaterializeHost(); // 3. force the host to build now, deterministically
await SeedAsync(host); // 4. seed known data
```
基类中添加了大量关于每一步背后*原因*的注释——它是为了让人阅读而设计的。它还集中了每个测试所需的辅助方法:用于对数据库进行断言的 `ExecuteDbContextAsync(...)`、用于深入 DI 的 `GetService()` / `CreateScope()`,以及一个包含已知 fixture 的已播种 `Stocks` 数组。
### 6. 确定性时间与生成的测试数据
`TimeProvider` 被注入并在测试 host 中被替换,因此时间戳是可控的,而不是依赖于系统墙上时钟。请求的 payload 是使用 [AutoFixture](https://github.com/AutoFixture/AutoFixture) 生成的,这使得测试可以专注于行为本身,而不是手写的样本数据。
### 整合起来
一个完整的测试从上到下读起来就是:*准备身份标识 → 通过 HTTP 执行操作 → 断言结果*,所有的底层基础设施都隐藏在了基类背后:
```
var request = CreateRequest();
var client = CreateClient().WithJwtAuth(claims => claims.AsAdmin()).Build();
var response = await client.PostAsJsonAsync("/stocks", request);
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var created = await response.Content.ReadFromJsonAsync();
Assert.Equal(request.Ticker, created!.Ticker);
```
刚才那个测试就已经针对真实的 PostgreSQL 和 SQLite 运行过了。
## 许可证
基于 [MIT License](LICENSE) 发布——请随意将这些测试模式复制到你自己的项目中。
## 专为 ASP.NET Core 构建的授权管理插件
**将组织架构树转化为 Claims。**
大多数授权规则都与组、职位角色和人员有关。这已经实现过很多次了,为什么还要重新造轮子呢?
[AuthorizationHub](https://authorizationhub.com) 的强大之处在于,当用户的请求在 ASP.NET Core pipeline 中被处理时,与用户相关联的租户、组和角色都会转化为身份 claims。这些 claims 是特定于你的应用程序的,并且可以在授权策略中使用。这与 ASP.NET Core 的安全模型完美契合,它意味着你可以通过更改用户的角色和组成员身份,来改变谁可以在你的应用程序中执行操作。无需修改代码并重新部署 Web 应用程序。
📺 Stop Inheriting Chaos — A Better Way to Write Integration Tests (Part 1)
标签:ASP.NET Core, Syscall, Web开发, 单元测试, 测试用例, 测试自动化, 请求拦截, 集成测试