# Testing Strategy The test project uses xUnit for the framework, Shouldly for assertions, and NSubstitute for mocking. ## Project Structure ``` tests/ ├── 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 ├── JdeScoping.Api.IntegrationTests/ │ └── ... (API integration tests) └── JdeScoping.Database.Tests/ ├── Infrastructure/ │ └── DatabaseTestBase.cs ├── Functions/ │ ├── ScalarFunctionTests.cs │ ├── SimpleTableFunctionTests.cs │ └── ComplexTableFunctionTests.cs └── Procedures/ └── ValidateSearchCriteriaProcedureTests.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 } } ``` ## Database Tests The `JdeScoping.Database.Tests` project tests SQL functions and stored procedures against a live SQL Server instance. ### Test Infrastructure Tests inherit from `DatabaseTestBase` which provides: - Connection management via `DatabaseTestFixture` - Automatic test data cleanup - Helper methods for inserting test searches ```csharp [Collection("DatabaseTests")] public class ScalarFunctionTests : DatabaseTestBase { [Fact] public async Task fn_GetSearchMinimumDt_ValidSearch_ReturnsDateTime() { // Arrange var criteria = new SearchCriteria { MinimumDt = new DateTime(2024, 6, 15) }; var searchId = await InsertTestSearchAsync(criteria); // Act var result = await Connection.QuerySingleOrDefaultAsync( "SELECT dbo.fn_GetSearchMinimumDt(@SearchId)", new { SearchId = searchId }); // Assert result.Should().BeCloseTo(criteria.MinimumDt.Value, TimeSpan.FromSeconds(1)); } } ``` ### Test Categories | Category | Tests | Description | |----------|-------|-------------| | Scalar Functions | 15 | Test `fn_GetSearchMinimumDt`, `fn_GetSearchMaximumDt`, `fn_GetSearchExtractMisData` | | Simple Table Functions | 38 | Test array extraction for work orders, items, profit centers, work centers, operators | | Complex Table Functions | 23 | Test object extraction for component lots and part operations | | Validation Procedure | 6 | Test `usp_ValidateSearchCriteria` error handling | **Total: 82 database tests** ### Running Database Tests ```bash # Run all database tests dotnet test tests/JdeScoping.Database.Tests # Run specific test class dotnet test tests/JdeScoping.Database.Tests --filter "FullyQualifiedName~ScalarFunctionTests" ``` **Prerequisites**: SQL Server must be running on localhost:1434 with the ScopingTool database. ## Related Documentation - [Core Project](./CoreProject.md) - [Dependencies](./Dependencies.md) - [Database](./Database.md)