Testing

How to write tests for Swap.Htmx applications.

Overview

Swap.Htmx applications are standard ASP.NET Core MVC apps, so you can use familiar testing tools:

  • xUnit / NUnit / MSTest — Test frameworks
  • Moq / NSubstitute — Mocking
  • Microsoft.AspNetCore.Mvc.Testing — Integration tests

Additionally, the Swap.Testing package provides helpers specifically for testing Swap responses.


Unit Testing Controllers

Since SwapController methods return IActionResult, you can test them like any MVC controller.

public class TodosControllerTests
{
    [Fact]
    public void Index_ReturnsViewResult()
    {
        // Arrange
        var controller = new TodosController();
        
        // Act
        var result = controller.Index();
        
        // Assert
        var viewResult = Assert.IsType<ViewResult>(result);
        Assert.NotNull(viewResult.Model);
    }
}

Testing SwapResponse

When testing actions that return SwapResponse, you can inspect the response builder:

[Fact]
public void Add_ReturnsSwapResponseWithToast()
{
    // Arrange
    var controller = new TodosController();
    
    // Act
    var result = controller.Add("New Task");
    
    // Assert
    var swapResult = Assert.IsType<SwapActionResult>(result);
    // Check that toast was added, targets were set, etc.
}

Integration Testing

Use WebApplicationFactory for full request/response testing.

public class TodosIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public TodosIntegrationTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task Index_ReturnsSuccessAndCorrectContentType()
    {
        // Act
        var response = await _client.GetAsync("/todos");

        // Assert
        response.EnsureSuccessStatusCode();
        Assert.Equal("text/html; charset=utf-8", 
            response.Content.Headers.ContentType?.ToString());
    }

    [Fact]
    public async Task Index_WithHxRequest_ReturnsPartial()
    {
        // Arrange - Simulate HTMX request
        var request = new HttpRequestMessage(HttpMethod.Get, "/todos");
        request.Headers.Add("HX-Request", "true");

        // Act
        var response = await _client.SendAsync(request);

        // Assert
        response.EnsureSuccessStatusCode();
        var content = await response.Content.ReadAsStringAsync();
        
        // Partial should NOT contain <html> or <body> tags
        Assert.DoesNotContain("<html", content);
        Assert.DoesNotContain("<body", content);
    }
}

Testing Event Handlers

SwapHandler classes can be tested in isolation by invoking HandleAsync directly.

public class StatsHandlerTests
{
    [Fact]
    public async Task HandleAsync_UpdatesStatsElement()
    {
        // Arrange
        var handler = new StatsHandler(Mock.Of<IStatsService>());
        var builder = new SwapResponseBuilder();
        var evt = new TaskCompletedEvent(1);

        // Act
        await handler.HandleAsync(evt, builder, CancellationToken.None);

        // Assert
        // Verify builder contains expected AlsoUpdate call
        // (Implementation depends on SwapResponseBuilder inspection API)
    }
}

Best Practices

  1. Test behavior, not implementation — Focus on what the user sees (correct HTML returned), not internal method calls.

  2. Use integration tests for HTMX flows — Since Swap.Htmx relies on HTTP headers (HX-Request), integration tests catch issues that unit tests miss.

  3. Mock external services — Inject mock repositories/services into controllers to isolate logic.

  4. Test both browser and HTMX paths — Remember that SwapView behaves differently based on request type. Test both.