# Testing Strategy The test project uses xUnit for the framework, Shouldly for assertions, and NSubstitute for mocking. ## Project Structure ``` JdeScoping.Tests/ ├── Unit/ │ ├── Services/ │ │ ├── SearchServiceTests.cs │ │ ├── ExcelExportServiceTests.cs │ │ └── DataSyncOrchestratorTests.cs │ ├── Repositories/ │ │ └── SearchRepositoryTests.cs │ └── Models/ │ └── SearchCriteriaTests.cs └── Integration/ ├── ApiTests/ │ ├── SearchControllerTests.cs │ └── LookupControllerTests.cs └── RepositoryTests/ └── JdeRepositoryTests.cs ``` ## Unit Tests Unit tests mock dependencies and test business logic in isolation: ```csharp public class SearchServiceTests { [Fact] public async Task ExecuteSearch_WithValidCriteria_ReturnsResults() { // Arrange var mockRepo = Substitute.For(); mockRepo.GetWorkOrdersAsync(Arg.Any()) .Returns(new List { new WorkOrder { Number = "WO123" } }); var service = new SearchService(mockRepo); var criteria = new SearchCriteria { ItemNumber = "ABC123" }; // Act var results = await service.ExecuteAsync(criteria); // Assert results.Count.ShouldBeGreaterThan(0); results.First().Number.ShouldBe("WO123"); } [Fact] public async Task ExecuteSearch_WithInvalidCriteria_ThrowsValidationException() { // Arrange var mockRepo = Substitute.For(); var service = new SearchService(mockRepo); var criteria = new SearchCriteria(); // Empty criteria // Act & Assert await Should.ThrowAsync( () => service.ExecuteAsync(criteria)); } } ``` ## Shouldly Assertions Shouldly provides readable assertion syntax without FluentAssertions licensing: ```csharp // Value assertions result.ShouldBe(expected); result.ShouldNotBeNull(); result.ShouldBeGreaterThan(0); // Collection assertions list.ShouldContain(item); list.ShouldBeEmpty(); list.Count.ShouldBe(5); // String assertions text.ShouldStartWith("Error"); text.ShouldContain("expected"); // Exception assertions Should.Throw(() => service.Process(null)); await Should.ThrowAsync(() => service.ProcessAsync()); ``` ## NSubstitute Mocking NSubstitute provides a simple API for creating test doubles: ```csharp // Create substitute var mockRepo = Substitute.For(); // Configure returns mockRepo.GetByIdAsync(123).Returns(new Search { Id = 123 }); mockRepo.GetByIdAsync(Arg.Any()).Returns(x => new Search { Id = (int)x[0] }); // Verify calls await mockRepo.Received().CreateAsync(Arg.Is(s => s.Status == "Queued")); await mockRepo.DidNotReceive().DeleteAsync(Arg.Any()); ``` ## Integration Tests Integration tests use `WebApplicationFactory` for API tests: ```csharp public class SearchControllerTests : IClassFixture> { private readonly HttpClient _client; public SearchControllerTests(WebApplicationFactory factory) { _client = factory.CreateClient(); } [Fact] public async Task SubmitSearch_ReturnsSearchId() { // Arrange var criteria = new SearchCriteria { ItemNumber = "TEST123" }; var content = new StringContent( JsonSerializer.Serialize(criteria), Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/search", content); // Assert response.StatusCode.ShouldBe(HttpStatusCode.OK); var result = await response.Content.ReadFromJsonAsync(); result.SearchId.ShouldBeGreaterThan(0); } } ``` ## Database Integration Tests Repository integration tests run against a local SQL Server instance: ```csharp public class SearchRepositoryIntegrationTests : IDisposable { private readonly string _connectionString; public SearchRepositoryIntegrationTests() { _connectionString = "Server=localhost;Database=LotFinder_Test;..."; // Setup test database } [Fact] public async Task CreateAndRetrieve_RoundTrips() { var repo = new SearchRepository(_connectionString); var search = new Search { UserId = "testuser", Status = "Queued" }; var id = await repo.CreateAsync(search); var retrieved = await repo.GetByIdAsync(id); retrieved.ShouldNotBeNull(); retrieved.UserId.ShouldBe("testuser"); } public void Dispose() { // Cleanup test data } } ``` ## Related Documentation - [Core Project](./CoreProject.md) - [Dependencies](./Dependencies.md)