Overview
The Intent.AspNetCore.IntegrationTests.CRUD module automatically generates integration tests for CRUD operations when using the Intent.Application.MediatR.CRUD module. This ensures your API endpoints are thoroughly tested with minimal manual effort.
Module ID : Intent.AspNetCore.IntegrationTests.CRUD
Version : 1.0.0+
Dependencies : Intent.AspNetCore.IntegrationTesting, Intent.Application.MediatR.CRUD
What Gets Generated
For each CRUD operation detected in your Services Designer, the module generates:
Create Tests Tests for entity creation with valid and invalid data
Read Tests Tests for retrieving single entities and lists
Update Tests Tests for updating entities with validation
Delete Tests Tests for deleting entities and handling not found cases
Installation
Intent.AspNetCore.IntegrationTests.CRUD
This module requires:
Intent.AspNetCore.IntegrationTesting
Intent.Application.MediatR.CRUD
Intent.Application.MediatR
How It Works
The module analyzes your Services Designer and automatically generates integration tests for operations that follow CRUD naming conventions:
Create : CreateProduct, AddCustomer
Read : GetProduct, GetProducts, FindCustomer
Update : UpdateProduct, ModifyCustomer
Delete : DeleteProduct, RemoveCustomer
Generated Test Examples
Create Operation Tests
using System . Net ;
using System . Net . Http . Json ;
using FluentAssertions ;
using Xunit ;
public class CreateProductTests : BaseIntegrationTest
{
public CreateProductTests ( IntegrationTestWebApplicationFactory factory )
: base ( factory ) { }
[ Fact ]
public async Task CreateProduct_WithValidData_ReturnsCreatedStatus ()
{
// Arrange
var command = new CreateProductCommand
{
Name = "Test Product" ,
Description = "A test product" ,
Price = 99.99m ,
CategoryId = TestData . CategoryId
};
// Act
var response = await Client . PostAsJsonAsync ( "/api/products" , command );
var productId = await response . Content . ReadFromJsonAsync < Guid >();
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . Created );
productId . Should (). NotBeEmpty ();
// Verify product was created in database
var getResponse = await Client . GetAsync ( $"/api/products/ { productId } " );
getResponse . StatusCode . Should (). Be ( HttpStatusCode . OK );
}
[ Fact ]
public async Task CreateProduct_WithEmptyName_ReturnsBadRequest ()
{
// Arrange
var command = new CreateProductCommand
{
Name = "" , // Invalid
Price = 99.99m
};
// Act
var response = await Client . PostAsJsonAsync ( "/api/products" , command );
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . BadRequest );
var problemDetails = await response . Content . ReadFromJsonAsync < ValidationProblemDetails >();
problemDetails . Errors . Should (). ContainKey ( "Name" );
}
[ Fact ]
public async Task CreateProduct_WithNegativePrice_ReturnsBadRequest ()
{
// Arrange
var command = new CreateProductCommand
{
Name = "Test Product" ,
Price = - 10m // Invalid
};
// Act
var response = await Client . PostAsJsonAsync ( "/api/products" , command );
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . BadRequest );
}
}
Read Operation Tests
public class GetProductTests : BaseIntegrationTest
{
public GetProductTests ( IntegrationTestWebApplicationFactory factory )
: base ( factory ) { }
[ Fact ]
public async Task GetProduct_WithValidId_ReturnsProduct ()
{
// Arrange
var productId = await CreateTestProduct ();
// Act
var response = await Client . GetAsync ( $"/api/products/ { productId } " );
var product = await response . Content . ReadFromJsonAsync < ProductDto >();
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . OK );
product . Should (). NotBeNull ();
product . Id . Should (). Be ( productId );
product . Name . Should (). NotBeNullOrEmpty ();
}
[ Fact ]
public async Task GetProduct_WithInvalidId_ReturnsNotFound ()
{
// Arrange
var nonExistentId = Guid . NewGuid ();
// Act
var response = await Client . GetAsync ( $"/api/products/ { nonExistentId } " );
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . NotFound );
}
[ Fact ]
public async Task GetProducts_ReturnsAllProducts ()
{
// Arrange
await CreateTestProduct ( "Product 1" );
await CreateTestProduct ( "Product 2" );
await CreateTestProduct ( "Product 3" );
// Act
var response = await Client . GetAsync ( "/api/products" );
var products = await response . Content . ReadFromJsonAsync < List < ProductDto >>();
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . OK );
products . Should (). HaveCountGreaterOrEqualTo ( 3 );
}
[ Fact ]
public async Task GetProducts_WithPagination_ReturnsPagedResults ()
{
// Act
var response = await Client . GetAsync ( "/api/products?pageNo=1&pageSize=10" );
var pagedResult = await response . Content . ReadFromJsonAsync < PagedResult < ProductDto >>();
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . OK );
pagedResult . Should (). NotBeNull ();
pagedResult . PageCount . Should (). BeGreaterThan ( 0 );
pagedResult . Data . Should (). HaveCount ( c => c <= 10 );
}
private async Task < Guid > CreateTestProduct ( string name = "Test Product" )
{
var command = new CreateProductCommand { Name = name , Price = 99.99m };
var response = await Client . PostAsJsonAsync ( "/api/products" , command );
return await response . Content . ReadFromJsonAsync < Guid >();
}
}
Update Operation Tests
public class UpdateProductTests : BaseIntegrationTest
{
public UpdateProductTests ( IntegrationTestWebApplicationFactory factory )
: base ( factory ) { }
[ Fact ]
public async Task UpdateProduct_WithValidData_ReturnsNoContent ()
{
// Arrange
var productId = await CreateTestProduct ();
var command = new UpdateProductCommand
{
Id = productId ,
Name = "Updated Product Name" ,
Price = 149.99m
};
// Act
var response = await Client . PutAsJsonAsync ( $"/api/products/ { productId } " , command );
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . NoContent );
// Verify update
var getResponse = await Client . GetAsync ( $"/api/products/ { productId } " );
var product = await getResponse . Content . ReadFromJsonAsync < ProductDto >();
product . Name . Should (). Be ( "Updated Product Name" );
product . Price . Should (). Be ( 149.99m );
}
[ Fact ]
public async Task UpdateProduct_WithInvalidId_ReturnsNotFound ()
{
// Arrange
var nonExistentId = Guid . NewGuid ();
var command = new UpdateProductCommand
{
Id = nonExistentId ,
Name = "Updated Name"
};
// Act
var response = await Client . PutAsJsonAsync ( $"/api/products/ { nonExistentId } " , command );
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . NotFound );
}
[ Fact ]
public async Task UpdateProduct_WithInvalidData_ReturnsBadRequest ()
{
// Arrange
var productId = await CreateTestProduct ();
var command = new UpdateProductCommand
{
Id = productId ,
Name = "" , // Invalid
Price = - 10m // Invalid
};
// Act
var response = await Client . PutAsJsonAsync ( $"/api/products/ { productId } " , command );
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . BadRequest );
}
private async Task < Guid > CreateTestProduct ()
{
var command = new CreateProductCommand { Name = "Test Product" , Price = 99.99m };
var response = await Client . PostAsJsonAsync ( "/api/products" , command );
return await response . Content . ReadFromJsonAsync < Guid >();
}
}
Delete Operation Tests
public class DeleteProductTests : BaseIntegrationTest
{
public DeleteProductTests ( IntegrationTestWebApplicationFactory factory )
: base ( factory ) { }
[ Fact ]
public async Task DeleteProduct_WithValidId_ReturnsNoContent ()
{
// Arrange
var productId = await CreateTestProduct ();
// Act
var response = await Client . DeleteAsync ( $"/api/products/ { productId } " );
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . NoContent );
// Verify deletion
var getResponse = await Client . GetAsync ( $"/api/products/ { productId } " );
getResponse . StatusCode . Should (). Be ( HttpStatusCode . NotFound );
}
[ Fact ]
public async Task DeleteProduct_WithInvalidId_ReturnsNotFound ()
{
// Arrange
var nonExistentId = Guid . NewGuid ();
// Act
var response = await Client . DeleteAsync ( $"/api/products/ { nonExistentId } " );
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . NotFound );
}
[ Fact ]
public async Task DeleteProduct_ThenGetProduct_ReturnsNotFound ()
{
// Arrange
var productId = await CreateTestProduct ();
// Act - Delete
await Client . DeleteAsync ( $"/api/products/ { productId } " );
// Act - Try to get deleted product
var response = await Client . GetAsync ( $"/api/products/ { productId } " );
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . NotFound );
}
private async Task < Guid > CreateTestProduct ()
{
var command = new CreateProductCommand { Name = "Test Product" , Price = 99.99m };
var response = await Client . PostAsJsonAsync ( "/api/products" , command );
return await response . Content . ReadFromJsonAsync < Guid >();
}
}
Advanced Scenarios
[ Fact ]
public async Task CreateOrder_WithLineItems_CreatesCompleteOrder ()
{
// Arrange
var command = new CreateOrderCommand
{
CustomerId = TestData . CustomerId ,
Items = new List < OrderItemDto >
{
new () { ProductId = TestData . ProductId1 , Quantity = 2 , UnitPrice = 50m },
new () { ProductId = TestData . ProductId2 , Quantity = 1 , UnitPrice = 75m }
}
};
// Act
var response = await Client . PostAsJsonAsync ( "/api/orders" , command );
var orderId = await response . Content . ReadFromJsonAsync < Guid >();
// Assert
response . StatusCode . Should (). Be ( HttpStatusCode . Created );
var order = await GetOrderFromApi ( orderId );
order . Items . Should (). HaveCount ( 2 );
order . TotalAmount . Should (). Be ( 175m );
}
Testing Concurrency
[ Fact ]
public async Task UpdateProduct_WithStaleData_ReturnsConflict ()
{
// Arrange
var productId = await CreateTestProduct ();
var product1 = await GetProduct ( productId );
var product2 = await GetProduct ( productId );
// Act - First update succeeds
product1 . Name = "First Update" ;
var response1 = await Client . PutAsJsonAsync ( $"/api/products/ { productId } " , product1 );
// Second update with stale data should fail
product2 . Name = "Second Update" ;
var response2 = await Client . PutAsJsonAsync ( $"/api/products/ { productId } " , product2 );
// Assert
response1 . StatusCode . Should (). Be ( HttpStatusCode . NoContent );
response2 . StatusCode . Should (). Be ( HttpStatusCode . Conflict );
}
Configuration
The module respects CRUD module configuration:
Create Operation Pattern
string
default: "Create*, Add*"
Patterns to detect create operations
Read Operation Pattern
string
default: "Get*, Find*"
Patterns to detect read operations
Update Operation Pattern
string
default: "Update*, Modify*"
Patterns to detect update operations
Delete Operation Pattern
string
default: "Delete*, Remove*"
Patterns to detect delete operations
Best Practices
Create unique test data for each test: private async Task < Guid > CreateTestProduct ( string name = null )
{
var uniqueName = name ?? $"Product- { Guid . NewGuid ()} " ;
var command = new CreateProductCommand { Name = uniqueName , Price = 99.99m };
var response = await Client . PostAsJsonAsync ( "/api/products" , command );
return await response . Content . ReadFromJsonAsync < Guid >();
}
Extract common operations into helper methods: protected async Task < ProductDto > GetProduct ( Guid id )
{
var response = await Client . GetAsync ( $"/api/products/ { id } " );
response . EnsureSuccessStatusCode ();
return await response . Content . ReadFromJsonAsync < ProductDto >();
}
Test all validation scenarios:
Required fields
Field length limits
Value ranges
Business rules
Referential integrity
Next Steps
Integration Testing Learn about the base integration testing infrastructure
CRUD Module Configure automatic CRUD implementation
MediatR Module Understand the CQRS pattern
FluentValidation Add validation rules to your commands