TailoredApps.Shared.DateTime¶
Description¶
This library solves one of the fundamental testability problems in .NET — direct use of System.DateTime.Now in production code, which prevents writing deterministic unit tests.
TailoredApps.Shared.DateTime provides the IDateTimeProvider interface and its default implementation DateTimeProvider. Instead of calling DateTime.Now directly, you inject IDateTimeProvider via DI and call provider.Now. In tests you swap the implementation for a mock that returns any point in time — making tests repeatable and independent of the system clock.
Instalacja¶
Rejestracja w DI¶
// Program.cs
using TailoredApps.Shared.DateTime;
builder.Services.AddSingleton<IDateTimeProvider, DateTimeProvider>();
Przykład użycia¶
Kod produkcyjny¶
public class OrderService
{
private readonly IDateTimeProvider _dateTime;
public OrderService(IDateTimeProvider dateTime)
{
_dateTime = dateTime;
}
public Order CreateOrder(string customerId, decimal amount)
{
return new Order
{
Id = Guid.NewGuid(),
CustomerId = customerId,
Amount = amount,
CreatedAt = _dateTime.UtcNow, // zamiast DateTime.UtcNow
ExpiresAt = _dateTime.UtcNow.AddDays(30)
};
}
public bool IsOrderExpired(Order order)
{
return order.ExpiresAt < _dateTime.UtcNow;
}
}
Test jednostkowy (Moq)¶
using Moq;
using TailoredApps.Shared.DateTime;
using Xunit;
public class OrderServiceTests
{
[Fact]
public void CreateOrder_ShouldSetCreatedAtToCurrentUtcTime()
{
// Arrange
var fixedTime = new DateTime(2024, 6, 1, 12, 0, 0, DateTimeKind.Utc);
var dateTimeMock = new Mock<IDateTimeProvider>();
dateTimeMock.Setup(d => d.UtcNow).Returns(fixedTime);
var service = new OrderService(dateTimeMock.Object);
// Act
var order = service.CreateOrder("customer-1", 99.99m);
// Assert
Assert.Equal(fixedTime, order.CreatedAt);
Assert.Equal(fixedTime.AddDays(30), order.ExpiresAt);
}
[Fact]
public void IsOrderExpired_WhenExpiresInPast_ReturnsTrue()
{
// Arrange
var now = new DateTime(2024, 6, 1, DateTimeKind.Utc);
var dateTimeMock = new Mock<IDateTimeProvider>();
dateTimeMock.Setup(d => d.UtcNow).Returns(now);
var service = new OrderService(dateTimeMock.Object);
var expiredOrder = new Order { ExpiresAt = now.AddDays(-1) };
// Act & Assert
Assert.True(service.IsOrderExpired(expiredOrder));
}
}
API Reference¶
| Typ | Rodzaj | Opis |
|---|---|---|
IDateTimeProvider |
Interfejs | Główny kontrakt — wszystkie właściwości do pobierania czasu |
DateTimeProvider |
Klasa | Implementacja produkcyjna — deleguje do System.DateTime |
IDateTimeProvider.Now |
Właściwość | Aktualny czas lokalny (DateTime.Now) |
IDateTimeProvider.UtcNow |
Właściwość | Aktualny czas UTC (DateTime.UtcNow) |
IDateTimeProvider.Today |
Właściwość | Aktualna data lokalna (DateTime.Today) |
IDateTimeProvider.UtcToday |
Właściwość | Aktualna data UTC (DateTime.UtcNow.Date) |
IDateTimeProvider.TimeOfDay |
Właściwość | Pora dnia (lokalnie) jako TimeSpan |
IDateTimeProvider.UtcTimeOfDaty |
Właściwość | Pora dnia UTC jako TimeSpan |
🤖 AI Agent Prompt¶
## TailoredApps.Shared.DateTime — Instrukcja dla agenta AI
Używasz biblioteki TailoredApps.Shared.DateTime w projekcie .NET.
### Rejestracja
```csharp
// Program.cs
builder.Services.AddSingleton<IDateTimeProvider, DateTimeProvider>();
Użycie¶
- Nigdy nie używaj
DateTime.NowaniDateTime.UtcNowbezpośrednio w kodzie produkcyjnym - Wstrzykuj
IDateTimeProviderprzez konstruktor - Używaj
provider.UtcNowdla timestampów w bazie danych - Używaj
provider.Nowtylko gdy potrzebujesz czasu lokalnego (np. do wyświetlania)
// ✅ Poprawnie
public class MyService
{
private readonly IDateTimeProvider _dateTime;
public MyService(IDateTimeProvider dateTime) => _dateTime = dateTime;
public DateTime GetExpiry() => _dateTime.UtcNow.AddHours(1);
}
// ❌ Niepoprawnie
public class MyService
{
public DateTime GetExpiry() => DateTime.UtcNow.AddHours(1); // nie testowalny!
}
Zasady¶
- Zawsze używaj
IDateTimeProviderzamiastSystem.DateTimebezpośrednio - W testach mockuj interfejs, aby zwracał stały punkt w czasie
- Preferuj
UtcNow/UtcTodaydla wartości zapisywanych w bazie danych ```