Testing Strategies
Overview
Section titled “Overview”How can we ensure that Specifications express the correct business rules? Is a single unit test enough, or do we need to verify compositions and Repository integration as well? This chapter systematically covers three testing levels for Specifications — individual Spec, composition, and Usecase integration.
Learning Objectives
Section titled “Learning Objectives”- Level 1: Spec self-testing - Boundary value testing of individual Specification’s
IsSatisfiedBy() - Level 2: Composition testing - Verifying correct behavior of
And,Or,Notcompositions - Level 3: Usecase testing - Verifying Specification integration through Mock Repository
Core Concepts
Section titled “Core Concepts”3-Level Test Pyramid
Section titled “3-Level Test Pyramid” / Level 3 \ Usecase tests (integration) / ----------- \ Verify Specs are correctly used via Mock Repository / Level 2 \ Composition tests (And/Or/Not) / --------------- \ Verify correct behavior of composite conditions / Level 1 \ Spec self-tests (boundary values) / ------------------- \ Verify satisfaction/non-satisfaction boundaries of IsSatisfiedBy()Level 1: Spec Self-Testing
Section titled “Level 1: Spec Self-Testing”Verify boundary values using Theory + InlineData.
[Theory][InlineData(0, false)] // Boundary: stock 0[InlineData(1, true)] // Boundary: stock 1[InlineData(100, true)] // Normal casepublic void ProductInStockSpec_ShouldReturnExpected_WhenStockIs(int stock, bool expected){ var product = new Product("Test", 1000, stock, "Test"); var spec = new ProductInStockSpec(); spec.IsSatisfiedBy(product).ShouldBe(expected);}Level 2: Composition Testing
Section titled “Level 2: Composition Testing”Verify And, Or, Not compositions with real data.
var spec = new ProductCategorySpec("Electronics") & new ProductInStockSpec();spec.IsSatisfiedBy(inStockElectronics).ShouldBeTrue();spec.IsSatisfiedBy(outOfStockElectronics).ShouldBeFalse();Level 3: Usecase Testing
Section titled “Level 3: Usecase Testing”Use a Mock Repository to verify that Specifications are correctly passed within Usecases.
public class MockProductRepository : IProductRepository{ public Specification<Product>? LastSpec { get; private set; } private readonly List<Product> _products; // ...}Project Description
Section titled “Project Description”Project Structure
Section titled “Project Structure”TestingStrategies/├── Product.cs├── IProductRepository.cs├── Specifications/│ ├── ProductInStockSpec.cs│ ├── ProductPriceRangeSpec.cs│ ├── ProductCategorySpec.cs│ └── ProductNameUniqueSpec.cs└── Program.cs
TestingStrategies.Tests.Unit/├── Level1_SpecSelfTests.cs # Spec boundary value tests├── Level2_CompositionTests.cs # And/Or/Not composition tests└── Level3_UsecaseTests.cs # Mock Repository integration testsAt a Glance
Section titled “At a Glance”A summary of what each test level verifies and the techniques used.
| Level | Target | What it verifies | Technique |
|---|---|---|---|
| Level 1 | Individual Spec | IsSatisfiedBy() boundary values | Theory + InlineData |
| Level 2 | Spec composition | And, Or, Not behavior | Real data + operators |
| Level 3 | Usecase | Spec correctly passed to Repository | Mock Repository |
Q1: Do I need to write all 3 levels?
Section titled “Q1: Do I need to write all 3 levels?”A: Level 1 is mandatory. Boundary value tests should be written for all Specifications. Level 2 is needed when there are complex compositions, and Level 3 when Usecases use Specifications.
Q2: Can I use a mocking framework like NSubstitute for Level 3?
Section titled “Q2: Can I use a mocking framework like NSubstitute for Level 3?”A: Yes, if you’re already using a mocking framework in your project. In this example, we implement Mock classes directly without external dependencies to clearly demonstrate the pattern.
Q3: What is the most common mistake in Specification testing?
Section titled “Q3: What is the most common mistake in Specification testing?”A: Missing boundary values. For example, if you don’t test the exact values 1000 and 10000 for ProductPriceRangeSpec(1000, 10000), you may miss bugs caused by the difference between >= and >.
We’ve ensured Specification correctness through testing. The next chapter covers architecture rules that use ArchUnitNET to automatically verify Specification class naming, folder placement, and inheritance rules.