Initial commit: JDE Scoping Tool migration project
Set up repository with legacy .NET Framework 4.8 source (OLD/), new .NET 10 Blazor solution (NEW/), OpenSpec specifications, documentation, and project configuration.
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# 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<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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Core Project](./CoreProject.md)
|
||||
- [Dependencies](./Dependencies.md)
|
||||
Reference in New Issue
Block a user