HHangfire Handbook

ORTA

Testing Strategies

Background job'lar normal .NET metotlarıdır — bu da test edilmelerini kolaylaştırır.

Unit Tests (Job method logic) En çok — Mock dependencies, test pure logic Integration Tests (Enqueue + Execute) Orta — InMemory storage, gerçek DI E2E (Full pipeline) Az — SQL + Server + Job

Karar Rehberi

Durum Öneri Örnek veya gerekçe
Job method logic Uygun: Unit test (mock DI) Mail gönderim servisini izole test et
Enqueue doğrulaması Uygun: Mock IBackgroundJobClient Controller'dan job enqueue edildiğini doğrula
Full pipeline Uygun: InMemory integration Job chain doğru sırada çalışıyor mu?
Cron schedule doğruluğu Uygun: Cronos kütüphanesiyle parse test "0 9 * * 1-5" gerçekten hafta içi mi?
Dashboard render test Uygun değil: Anlamsız UI Hangfire'a ait

Unit Test: Job Method

public class OrderNotificationServiceTests
{
    private readonly Mock<IEmailSender> _emailSender = new();
    private readonly Mock<IOrderRepository> _orderRepo = new();
    private readonly OrderNotificationService _sut;

    public OrderNotificationServiceTests()
    {
        _sut = new OrderNotificationService(_emailSender.Object, _orderRepo.Object);
    }

    [Fact]
    public async Task SendConfirmationAsync_ValidOrder_SendsEmail()
    {
        // Arrange
        var order = new Order { Id = 1, CustomerEmail = "test@example.com", Status = OrderStatus.Paid };
        _orderRepo.Setup(r => r.GetByIdAsync(1)).ReturnsAsync(order);

        // Act
        await _sut.SendConfirmationAsync(1);

        // Assert
        _emailSender.Verify(e => e.SendAsync(
            "test@example.com",
            It.IsAny<string>(),
            It.IsAny<string>()), Times.Once);
    }

    [Fact]
    public async Task SendConfirmationAsync_OrderNotFound_DoesNothing()
    {
        _orderRepo.Setup(r => r.GetByIdAsync(99)).ReturnsAsync((Order?)null);

        await _sut.SendConfirmationAsync(99);

        _emailSender.Verify(e => e.SendAsync(
            It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never);
    }
}

Mock IBackgroundJobClient

public class OrderControllerTests
{
    private readonly Mock<IBackgroundJobClient> _jobClient = new();
    private readonly Mock<IOrderService> _orderService = new();

    [Fact]
    public void CreateOrder_EnqueuesNotificationJob()
    {
        // Arrange
        var controller = new OrderController(_orderService.Object, _jobClient.Object);
        _orderService.Setup(s => s.Create(It.IsAny<CreateOrderRequest>())).Returns(42);

        // Act
        var result = controller.CreateOrder(new CreateOrderRequest());

        // Assert — Enqueue çağrıldı mı?
        // IBackgroundJobClient.Create(Job job, IState state) imzasını verify ediyoruz
        _jobClient.Verify(c => c.Create(
            It.Is<Job>(j =>
                j.Type == typeof(IOrderNotificationService) &&
                j.Method.Name == nameof(IOrderNotificationService.SendConfirmationAsync)),
            It.IsAny<EnqueuedState>()), Times.Once);
    }

    [Fact]
    public void CreateOrder_PassesCorrectOrderId()
    {
        var controller = new OrderController(_orderService.Object, _jobClient.Object);
        _orderService.Setup(s => s.Create(It.IsAny<CreateOrderRequest>())).Returns(42);

        controller.CreateOrder(new CreateOrderRequest());

        // Argüman kontrolü
        _jobClient.Verify(c => c.Create(
            It.Is<Job>(j => (int)j.Args[0] == 42),
            It.IsAny<EnqueuedState>()), Times.Once);
    }
}

Integration Test: InMemory Storage

# Gerekli paket — test projesine ekleyin
dotnet add package Hangfire.InMemory
public class HangfireIntegrationTests : IDisposable
{
    private readonly BackgroundJobServer _server;

    public HangfireIntegrationTests()
    {
        GlobalConfiguration.Configuration.UseInMemoryStorage();
        _server = new BackgroundJobServer();
    }

    [Fact]
    public async Task Enqueue_ProcessesJobSuccessfully()
    {
        var completed = new ManualResetEventSlim(false);

        BackgroundJob.Enqueue(() => SignalCompletion(completed));

        Assert.True(completed.Wait(TimeSpan.FromSeconds(10)));
    }

    public static void SignalCompletion(ManualResetEventSlim signal) => signal.Set();

    public void Dispose() => _server.Dispose();
}

Örnek: CI pipeline'da Hangfire job'larının unit test'leri her commit'te koşar (~100ms). Integration test'ler InMemory storage ile haftalık nightly build'de çalışır. SQL Server'lı E2E test'ler sadece release branch'inde koşar.