Skip to main content

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

CreateProductTests.cs
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

GetProductTests.cs
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

UpdateProductTests.cs
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

DeleteProductTests.cs
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

Build docs developers (and LLMs) love