c6aeb20d9c
- Add ExtractionFunctions.md reference document - Update database-schema spec with 11 extraction functions - Update data-access spec to document extraction function approach - Update search-processing spec with new query builder interface - Add Database.Tests to Testing.md architecture doc - Update DataFlow.md with extraction function flow
251 lines
7.4 KiB
Markdown
251 lines
7.4 KiB
Markdown
# 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<ISearchRepository>();
|
|
mockRepo.GetWorkOrdersAsync(Arg.Any<SearchCriteria>())
|
|
.Returns(new List<WorkOrder> { 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<ISearchRepository>();
|
|
var service = new SearchService(mockRepo);
|
|
var criteria = new SearchCriteria(); // Empty criteria
|
|
|
|
// Act & Assert
|
|
await Should.ThrowAsync<ValidationException>(
|
|
() => 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<ArgumentException>(() => service.Process(null));
|
|
await Should.ThrowAsync<InvalidOperationException>(() => service.ProcessAsync());
|
|
```
|
|
|
|
## NSubstitute Mocking
|
|
|
|
NSubstitute provides a simple API for creating test doubles:
|
|
|
|
```csharp
|
|
// Create substitute
|
|
var mockRepo = Substitute.For<ISearchRepository>();
|
|
|
|
// Configure returns
|
|
mockRepo.GetByIdAsync(123).Returns(new Search { Id = 123 });
|
|
mockRepo.GetByIdAsync(Arg.Any<int>()).Returns(x => new Search { Id = (int)x[0] });
|
|
|
|
// Verify calls
|
|
await mockRepo.Received().CreateAsync(Arg.Is<Search>(s => s.Status == "Queued"));
|
|
await mockRepo.DidNotReceive().DeleteAsync(Arg.Any<int>());
|
|
```
|
|
|
|
## Integration Tests
|
|
|
|
Integration tests use `WebApplicationFactory<Program>` for API tests:
|
|
|
|
```csharp
|
|
public class SearchControllerTests : IClassFixture<WebApplicationFactory<Program>>
|
|
{
|
|
private readonly HttpClient _client;
|
|
|
|
public SearchControllerTests(WebApplicationFactory<Program> 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<SearchResult>();
|
|
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<DateTime?>(
|
|
"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)
|