Test coverage strategy is about finding the tests that provide the most value for the least effort. Not everything needs a test — simple property assignments, trivial getters, and auto-generated code do not benefit from testing. High-value test targets are complex business rules, error paths that are hard to trigger manually, and critical security checks (ownership, authentication). Code coverage is a useful metric for finding gaps but should never be the primary goal.
BlogApp Testing Plan
// ── HIGH VALUE: PostsService business logic ────────────────────────────────
// 1. Post creation — happy path and slug uniqueness
[Fact]
public async Task CreateAsync_WhenSlugIsTaken_ThrowsConflictException()
{
_repo.Setup(r => r.SlugExistsAsync("taken-slug", default)).ReturnsAsync(true);
var request = new CreatePostRequest("Title", "taken-slug", "Body", "draft");
Func<Task> act = () => _sut.CreateAsync(request, default);
await act.Should().ThrowAsync<ConflictException>()
.WithMessage("*taken-slug*");
}
// 2. Post ownership check for update/delete
[Fact]
public async Task IsAuthorOrAdminAsync_WhenUserIsOwner_ReturnsTrue()
{
_repo.Setup(r => r.GetAuthorIdAsync(42, default)).ReturnsAsync("user-123");
_user.Setup(u => u.UserId).Returns("user-123");
_user.Setup(u => u.IsInRole("Admin")).Returns(false);
var result = await _sut.IsAuthorOrAdminAsync(42, "user-123", default);
result.Should().BeTrue();
}
[Fact]
public async Task IsAuthorOrAdminAsync_WhenUserIsAdmin_ReturnsTrueRegardlessOfOwnership()
{
_repo.Setup(r => r.GetAuthorIdAsync(42, default)).ReturnsAsync("other-user");
_user.Setup(u => u.IsInRole("Admin")).Returns(true);
var result = await _sut.IsAuthorOrAdminAsync(42, "admin-user", default);
result.Should().BeTrue();
}
// 3. JWT token claims
[Fact]
public void GenerateAccessToken_ContainsExpectedClaims()
{
var user = new ApplicationUser { Id = "u1", Email = "alice@example.com",
DisplayName = "Alice" };
var roles = new[] { "Author" };
var token = _tokenService.GenerateAccessToken(user, roles);
var claims = _tokenService.ParseClaims(token);
claims.Should().Contain(c => c.Type == "sub" && c.Value == "u1");
claims.Should().Contain(c => c.Type == "email" && c.Value == "alice@example.com");
claims.Should().Contain(c => c.Type == "role" && c.Value == "Author");
}
// ── LOW VALUE: skip these ──────────────────────────────────────────────────
// ❌ Testing EF Core DbContext setup (framework code)
// ❌ Testing that appsettings.json is bound correctly (configuration)
// ❌ Testing AutoMapper/Mapster mappings for simple field-to-field copies
// ❌ Testing that Console.WriteLine was called (trivial side effect)
// ❌ Testing that an empty list is returned when the database is empty
// (unless there is specific behaviour expected — filtering, ordering)
//
// CODE COVERAGE TARGET: 75-85% line coverage on Services layer
// 60-70% line coverage overall (lower due to infra code)
//
// Coverage command: dotnet test --collect:"XPlat Code Coverage"
// reportgenerator -reports:coverage.xml -targetdir:coverage-report
BlogApp Testing Plan Summary
| Layer | Test Count | Framework | Focus |
|---|---|---|---|
| PostsService | 25 unit | xUnit + Moq | Business rules, error paths |
| AuthService / JWT | 15 unit | xUnit + Moq | Token generation, claim extraction |
| SlugGenerator | 10 unit | xUnit [Theory] | Input/output pairs |
| PostsController | 20 integration | WebApplicationFactory | HTTP pipeline, status codes |
| AuthController | 15 integration | WebApplicationFactory | Login, refresh, logout |
| Angular Services | 30 unit | Jasmine + SpyObj | API calls, error handling |
| Angular Components | 40 unit | TestBed + Testing Library | Template rendering, interactions |
| E2E Flows | 20 E2E | Cypress | Critical user journeys |
Common Mistakes
Mistake 1 — 100% coverage target leads to testing trivial code
❌ Wrong — testing every auto-property getter to reach 100%; coverage metric satisfied but no meaningful bug prevention.
✅ Correct — target 80% with focus on business logic; use coverage reports to find gaps in critical paths.
Mistake 2 — No tests for error paths (happy path only)
❌ Wrong — only testing successful operations; all error handling is untested and likely broken.
✅ Correct — at least one test per error path: NotFoundException, ConflictException, 401, 403, 412.