profileShare

rasmusjy / splitapp-backend-clean-onion

Read-only snapshot

No repository description.

main default branch 429 files Expires Sep 13, 2026, 9:06 AM
ExpenseServiceTests.cs 3,235 bytes
1 using App.BLL.DTO;
2 using App.BLL.Services;
3 using App.Domain;
4 using App.Domain.Contracts;
5 using Base.Contracts;
6 using FluentAssertions;
7 using Moq;
8
9 namespace App.Tests.BLL;
10
11 public class ExpenseServiceTests
12 {
13 private readonly Mock<IAppUnitOfWork> _uow = new();
14 private readonly Mock<IExpenseRepository> _expenses = new();
15 private readonly Mock<ITripParticipantRepository> _participants = new();
16 private readonly Mock<IBaseRepository<ExpenseSplit>> _splitsRepo = new();
17 private readonly ExpenseService _sut;
18
19 public ExpenseServiceTests()
20 {
21 _uow.Setup(u => u.Expenses).Returns(_expenses.Object);
22 _uow.Setup(u => u.TripParticipants).Returns(_participants.Object);
23 _uow.Setup(u => u.GetRepository<ExpenseSplit>()).Returns(_splitsRepo.Object);
24 _sut = new ExpenseService(_uow.Object);
25 }
26
27 [Fact]
28 public async Task CreateExpenseWithSplitsAsync_EqualAll_CreatesSplitForEachParticipant()
29 {
30 // Arrange — 2 participants, expense 100 → each gets 50
31 var tripId = Guid.NewGuid();
32 var userA = Guid.NewGuid();
33 var userB = Guid.NewGuid();
34 var dto = new ExpenseBllDto
35 {
36 TripId = tripId,
37 PaidByUserId = userA,
38 Amount = 100m,
39 ExpenseDate = DateTime.UtcNow,
40 SplitMethod = ESplitMethod.EqualAll
41 };
42
43 _participants.Setup(r => r.GetByTripIdAsync(tripId)).ReturnsAsync(new List<TripParticipant>
44 {
45 new() { TripId = tripId, UserId = userA, IsActive = true },
46 new() { TripId = tripId, UserId = userB, IsActive = true }
47 });
48 _expenses.Setup(r => r.Add(It.IsAny<Expense>())).Returns((Expense e) => e);
49 _splitsRepo.Setup(r => r.Add(It.IsAny<ExpenseSplit>())).Returns((ExpenseSplit s) => s);
50
51 // Act
52 await _sut.CreateExpenseWithSplitsAsync(dto, Array.Empty<Guid>(), Array.Empty<decimal>(), Array.Empty<decimal>());
53
54 // Assert
55 _expenses.Verify(r => r.Add(It.IsAny<Expense>()), Times.Once);
56 _splitsRepo.Verify(r => r.Add(It.Is<ExpenseSplit>(s => s.Amount == 50m)), Times.Exactly(2));
57 }
58
59 [Fact]
60 public async Task CreateExpenseWithSplitsAsync_EqualAll_WhenNoParticipants_CreatesExpenseButNoSplits()
61 {
62 // Arrange — edge case: trip with zero active participants. Expense should still
63 // be added (organizer might have left), but no splits get generated.
64 var tripId = Guid.NewGuid();
65 var dto = new ExpenseBllDto
66 {
67 TripId = tripId,
68 PaidByUserId = Guid.NewGuid(),
69 Amount = 100m,
70 ExpenseDate = DateTime.UtcNow,
71 SplitMethod = ESplitMethod.EqualAll
72 };
73
74 _participants.Setup(r => r.GetByTripIdAsync(tripId)).ReturnsAsync(new List<TripParticipant>());
75 _expenses.Setup(r => r.Add(It.IsAny<Expense>())).Returns((Expense e) => e);
76
77 // Act
78 await _sut.CreateExpenseWithSplitsAsync(dto, Array.Empty<Guid>(), Array.Empty<decimal>(), Array.Empty<decimal>());
79
80 // Assert
81 _expenses.Verify(r => r.Add(It.IsAny<Expense>()), Times.Once);
82 _splitsRepo.Verify(r => r.Add(It.IsAny<ExpenseSplit>()), Times.Never);
83 }
84 }
85