Software Testing and Quality Assurance

Tests usually pass for reasons unrelated to whether the code under test actually works. Line coverage reports green, the CI dashboard is clean, and a bug ships to production through a code path every test technically executed. The articles in this collection start from that observation: the value of a test is what it would catch on failure, not what it asserts on success.

Mutation testing is the recurring theme because it answers the question coverage cannot: if the implementation changes in a small, semantically meaningful way, do any tests notice? Stryker.NET introduces deliberate faults — flipping > to >=, replacing return values, removing method calls — and reports which mutations survive. Surviving mutations are blind spots dressed up as test coverage, and the surprise during the first run is rarely small.

TimeProvider adoption is the second recurring topic. DateTime.UtcNow is a hidden dependency that makes deterministic testing impossible and silently produces bugs that only surface at midnight, on the last day of the month, or across daylight saving transitions. .NET 8 shipped the abstraction in 2023; most codebases still ignore it. The articles cover how to introduce TimeProvider to legacy code without rewriting half the project and what tests become possible once time is no longer hardcoded.

Integration tests get specific attention because the failure mode is different. Unit tests prove that a method does what its author intended; integration tests prove that the wiring around it still holds. The trade-offs between in-memory fakes, Testcontainers, and full deployed environments are covered with the costs named — test execution time, flakiness budgets, and the maintenance overhead each option implies.

A separate cluster examines what tests claim to prove versus what they actually prove: contract tests that miss the contracts that matter, snapshot tests that lock in bugs as expected output, and the coverage thresholds that look rigorous in CI but mean nothing in production.

TUnit.Mocks: No Castle, No Reflection, No Drama

TUnit.Mocks: No Castle, No Reflection, No Drama

Most .NET test projects rely on NSubstitute and Castle.DynamicProxy. That foundation is cracking: NativeAOT breaks it, trimming strips it, cold-start costs accumulate. TUnit.Mocks takes a different route: a source generator emits typed mocks at compile time, IService.Mock() (or Mock.Of<T>()) is the entry point, runtime reflection is gone.
You're Shipping Bugs Faster, and Your Tests Are Helping

You're Shipping Bugs Faster, and Your Tests Are Helping

AI coding agents make you faster. They also make your bugs faster: they generate plausible-looking code, you review quickly, the tests pass, and production breaks two weeks later. I keep falling into this pattern myself, always on the happy path, telling myself the edge cases come later. They don’t. Here’s the pattern, why most test frameworks make it worse, and how TUnit fights it.
Stop Pretending TimeProvider Doesn't Exist

Stop Pretending TimeProvider Doesn't Exist

DateTime.UtcNow looks harmless. It is not. It is a hidden dependency you cannot control in tests, cannot reproduce in staging, and cannot freeze to catch the bugs that only surface at midnight or on the last day of the month. .NET 8 shipped TimeProvider to fix this. Two years on, most codebases still ignore it. Some planned to adopt it later. Later has not arrived.
Security Tests That Prove Themselves

Security Tests That Prove Themselves

Your security tests run. They pass. But can you prove when they ran and against which code version? Most security testing lives in Word documents, Postman exports, and screenshot folders on SharePoint. The tests themselves might be valid. The evidence trail is not. This article shows how to build CLI-based test suites using xUnit and WebApplicationFactory that generate their own proof: structured logs with timestamps, commit hashes, and correlation IDs captured automatically in CI/CD pipelines. No more quarterly reports that could have been written yesterday. Instead, 847 test executions across 23 deployments, each linked to a specific commit and preserved for 90 days.
"Just Delete the User": Famous Last Words Before the GDPR Audit

"Just Delete the User": Famous Last Words Before the GDPR Audit

Your PM thinks erasure is a quick database DELETE. Three weeks later, you’ve found user data in seventeen places: production DB, analytics warehouse, Redis cache, Elasticsearch, backup tapes, and that legacy system nobody dares touch. “Delete” actually means orchestrating coordinated erasure across distributed systems, maintaining audit trails, notifying third parties, and proving it worked. This guide shows the fatal patterns I’ve seen fail spectacularly, then walks through proper orchestration with Azure Durable Functions, soft-delete with anonymization, verification checks, and immutable audit logs.