DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on

C# Architecture Mastery — Testing Strategies in Clean Architecture (.NET) (Part 7)

C# Architecture Mastery — Testing Strategies in Clean Architecture (.NET) (Part 7)

C# Architecture Mastery — Testing Strategies in Clean Architecture (.NET) (Part 7)

Most teams say they “have tests”.

Very few have a testing strategy.

In Clean Architecture, testing is not an afterthought — it is a design outcome.
If your system is hard to test, your architecture is already telling you something is wrong.

In this Part 7, we’ll cover how testing aligns with Clean Architecture in .NET, what to test at each layer, and how senior teams avoid brittle test suites.


1. Clean Architecture Changes What You Test

Traditional systems test from the outside in.

Clean Architecture tests from the inside out.

Priority order:

  1. Domain logic
  2. Application use cases
  3. Infrastructure integrations
  4. Web / API layer

This ordering is intentional.


2. The Testing Pyramid (Clean Architecture Edition)

Classic testing pyramid:

  • Unit tests (many)
  • Integration tests (some)
  • End‑to‑end tests (few)

Clean Architecture sharpens it:

Domain Tests        ███████████
Application Tests   ████████
Integration Tests   ████
API / E2E Tests     ██
Enter fullscreen mode Exit fullscreen mode

Business rules deserve the most confidence.


3. Domain Tests — Pure and Fast

What to test

  • Business rules
  • Invariants
  • Calculations
  • State transitions

What NOT to test

  • Databases
  • HTTP
  • Framework behavior
// ✅ Domain unit test
[Fact]
public void Order_Cannot_Be_Created_With_Negative_Total()
{
    var act = () => new Order(-10);
    act.Should().Throw<DomainException>();
}
Enter fullscreen mode Exit fullscreen mode

If domain tests require mocks, something is wrong.


4. Application Layer Tests — Use Cases

What to test

  • Orchestration logic
  • Decision paths
  • Interactions with abstractions
// ✅ Application test with mocks
[Fact]
public async Task CreateOrder_Saves_Order_And_Sends_Notification()
{
    var repo = Substitute.For<IOrderRepository>();
    var notifier = Substitute.For<IOrderNotifier>();

    var useCase = new CreateOrderUseCase(repo, notifier);

    await useCase.Execute(new CreateOrderCommand(100));

    await repo.Received(1).SaveAsync(Arg.Any<Order>());
    await notifier.Received(1).NotifyAsync(Arg.Any<Order>());
}
Enter fullscreen mode Exit fullscreen mode

Mocks belong only at the boundaries.


5. Infrastructure Tests — Integration by Nature

Infrastructure tests answer:

“Does this thing actually work?”

Examples

  • EF Core mappings
  • SQL queries
  • External APIs
  • File systems
  • Message brokers
// ✅ EF Core integration test
using var db = CreateRealDbContext();
var repo = new OrderRepository(db);

await repo.SaveAsync(order);
var saved = await db.Orders.FindAsync(order.Id);

saved.Should().NotBeNull();
Enter fullscreen mode Exit fullscreen mode

These tests are:

  • Slower
  • Fewer
  • Essential

6. API Tests — Thin and Focused

API tests should validate:

  • Routing
  • HTTP contracts
  • Serialization
  • Authentication / authorization

They should not re-test business rules.

// ✅ API test
var response = await client.PostAsJsonAsync("/orders", request);
response.StatusCode.Should().Be(HttpStatusCode.OK);
Enter fullscreen mode Exit fullscreen mode

If API tests are complex, controllers are probably fat.


7. What NOT to Test (Senior Rule)

Do not test:

  • Framework internals
  • EF Core itself
  • ASP.NET Core routing logic
  • Microsoft libraries

Test your code, not theirs.


8. Common Testing Smells

🚨 Warning signs:

  • Mocking DbContext everywhere
  • Integration tests replacing unit tests
  • Tests coupled to HTTP models
  • Tests breaking on refactors
  • Massive test setup code

These usually indicate boundary violations.


9. The Real Goal of Testing

Testing is not about coverage.

It is about:

  • Confidence
  • Change safety
  • Design feedback

Good tests make refactoring boring.


10. Senior-Level Testing Checklist

Before shipping, ask:

  1. Are business rules tested without infrastructure?
  2. Do use cases have focused unit tests?
  3. Are integrations tested against real systems?
  4. Are API tests thin?
  5. Do tests guide design decisions?

If yes, your architecture is working.


Final Thoughts

Clean Architecture does not guarantee testability.

Testability proves Clean Architecture.

If testing feels painful:

  • Revisit boundaries
  • Remove leaks
  • Simplify responsibilities

Great tests are a side-effect of great architecture.

✍️ Written by Cristian Sifuentes — helping teams design systems that are easy to test, change, and trust.

Top comments (0)