Make AnalyticsService mockable via `AnalyticsServiceProtocol (#5599)

* Create AnalyticsServiceProtocol and conform AnalyticsService

* Replace AnalyticsService properties and arguments with AnalyticsServiceProtocol

* Inline AnalyticsServiceMock replacing old mock

* handle more complex AnalyticsServiceMock callsites

* move documentation from AnalyticsService to AnalyticsServiceProtocol
This commit is contained in:
Michael
2026-05-20 00:23:58 -05:00
committed by GitHub
parent 946cb53559
commit 5a07c98afc
118 changed files with 1218 additions and 505 deletions
@@ -26,33 +26,29 @@ struct RoomDetailsScreenViewModelTests {
init() {
AppSettings.resetAllSettings()
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
cancellables.removeAll()
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test"))
notificationSettingsProxyMock = NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration())
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: notificationSettingsProxyMock,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
}
@Test
mutating func leaveRoomTappedWhenPublic() async throws {
let appSettings = AppSettings()
let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", members: mockedMembers, joinRule: .public))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
let deferred = deferFulfillment(context.observe(\.viewState.bindings.leaveRoomAlertItem)) { $0 != nil }
context.send(viewAction: .processTapLeave)
@@ -64,17 +60,15 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func leaveRoomTappedWhenRoomNotPublic() async throws {
let appSettings = AppSettings()
let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", members: mockedMembers))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
let deferred = deferFulfillment(context.observe(\.viewState.bindings.leaveRoomAlertItem)) { $0 != nil }
context.send(viewAction: .processTapLeave)
@@ -87,17 +81,15 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func leaveRoomTappedWithLessThanTwoMembers() {
let appSettings = AppSettings()
let mockedMembers: [RoomMemberProxyMock] = [.mockAlice]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", members: mockedMembers))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
context.send(viewAction: .processTapLeave)
#expect(context.leaveRoomAlertItem?.state == .empty)
@@ -146,18 +138,16 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func initialDMDetailsState() async throws {
let appSettings = AppSettings()
let recipient = RoomMemberProxyMock.mockDan
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
let deferred = deferFulfillment(viewModel.context.observe(\.viewState.dmRecipientInfo)) { $0 != nil }
@@ -168,19 +158,17 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func ignoreSuccess() async throws {
let appSettings = AppSettings()
let recipient = RoomMemberProxyMock.mockDan
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
let deferredRecipient = deferFulfillment(viewModel.context.observe(\.viewState.dmRecipientInfo)) { $0 != nil }
@@ -200,8 +188,6 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func ignoreFailure() async throws {
let appSettings = AppSettings()
let recipient = RoomMemberProxyMock.mockDan
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
let clientProxy = ClientProxyMock(.init())
@@ -209,11 +195,11 @@ struct RoomDetailsScreenViewModelTests {
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init(clientProxy: clientProxy)),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
let deferredRecipient = deferFulfillment(viewModel.context.observe(\.viewState.dmRecipientInfo)) { $0 != nil }
@@ -234,18 +220,16 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func unignoreSuccess() async throws {
let appSettings = AppSettings()
let recipient = RoomMemberProxyMock.mockIgnored
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
let deferredRecipient = deferFulfillment(viewModel.context.observe(\.viewState.dmRecipientInfo)) { $0 != nil }
@@ -265,8 +249,6 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func unignoreFailure() async throws {
let appSettings = AppSettings()
let recipient = RoomMemberProxyMock.mockIgnored
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
let clientProxy = ClientProxyMock(.init())
@@ -274,11 +256,11 @@ struct RoomDetailsScreenViewModelTests {
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init(clientProxy: clientProxy)),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
let deferredRecipient = deferFulfillment(viewModel.context.observe(\.viewState.dmRecipientInfo)) { $0 != nil }
@@ -299,8 +281,6 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func cannotInvitePeople() async {
let appSettings = AppSettings()
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockAlice]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test",
members: mockedMembers,
@@ -308,11 +288,11 @@ struct RoomDetailsScreenViewModelTests {
powerLevelsConfiguration: .init(canUserInvite: false)))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
@@ -321,17 +301,15 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func invitePeople() async {
let appSettings = AppSettings()
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", members: mockedMembers, joinRule: .public))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
@@ -356,8 +334,6 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func canEditAvatar() async {
let appSettings = AppSettings()
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice]
let configuration = JoinedRoomProxyMockConfiguration(name: "Test",
@@ -381,11 +357,11 @@ struct RoomDetailsScreenViewModelTests {
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
@@ -397,8 +373,6 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func canEditName() async {
let appSettings = AppSettings()
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice]
let configuration = JoinedRoomProxyMockConfiguration(name: "Test",
@@ -422,11 +396,11 @@ struct RoomDetailsScreenViewModelTests {
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
@@ -438,8 +412,6 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func canEditTopic() async {
let appSettings = AppSettings()
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice]
let configuration = JoinedRoomProxyMockConfiguration(name: "Test",
@@ -463,11 +435,11 @@ struct RoomDetailsScreenViewModelTests {
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
@@ -479,17 +451,15 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func cannotEditRoom() async {
let appSettings = AppSettings()
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: false, members: mockedMembers))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
@@ -501,17 +471,15 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func cannotEditDirectRoom() async {
let appSettings = AppSettings()
let mockedMembers: [RoomMemberProxyMock] = [.mockMeAdmin, .mockBob, .mockAlice]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, members: mockedMembers))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
@@ -522,16 +490,14 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func notificationLoadingSettingsFailure() async throws {
let appSettings = AppSettings()
notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneThrowableError = NotificationSettingsError.Generic(msg: "error")
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: notificationSettingsProxyMock,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
var deferred = deferFulfillment(context.observe(\.viewState.notificationSettingsState)) { $0.isError }
@@ -731,17 +697,15 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func knockRequestsCounter() async throws {
let appSettings = AppSettings()
let mockedRequests: [KnockRequestProxyMock] = [.init(), .init()]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: false, knockRequestsState: .loaded(mockedRequests), joinRule: .knock))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: notificationSettingsProxyMock,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
let deferred = deferFulfillment(context.observe(\.viewState)) { state in
state.knockRequestsCount == 2 && state.canSeeKnockingRequests
@@ -755,16 +719,14 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func knockRequestsCounterIsLoading() async throws {
let appSettings = AppSettings()
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: false, knockRequestsState: .loading, joinRule: .knock))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: notificationSettingsProxyMock,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
let deferred = deferFulfillment(context.observe(\.viewState)) { state in
state.knockRequestsCount == 0 && state.canSeeKnockingRequests
@@ -775,8 +737,6 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func knockRequestsCounterIsNotShownIfNoPermissions() async throws {
let appSettings = AppSettings()
let mockedRequests: [KnockRequestProxyMock] = [.init(), .init()]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test",
isDirect: false,
@@ -785,11 +745,11 @@ struct RoomDetailsScreenViewModelTests {
powerLevelsConfiguration: .init(canUserInvite: false)))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: notificationSettingsProxyMock,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
let deferred = deferFulfillment(context.observe(\.viewState)) { state in
state.knockRequestsCount == 2 &&
@@ -803,18 +763,16 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func knockRequestsCounterIsNotShownIfDM() async throws {
let appSettings = AppSettings()
let mockedRequests: [KnockRequestProxyMock] = [.init(), .init()]
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockAlice]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, members: mockedMembers, knockRequestsState: .loaded(mockedRequests), joinRule: .knock))
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: notificationSettingsProxyMock,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
let deferred = deferFulfillment(context.observe(\.viewState)) { state in
state.knockRequestsCount == 2 &&
@@ -830,8 +788,6 @@ struct RoomDetailsScreenViewModelTests {
@Test
mutating func historySharingPillDisplayed() async throws {
let appSettings = AppSettings()
let configuration = JoinedRoomProxyMockConfiguration(historyVisibility: .shared)
let infoSubject = CurrentValueSubject<RoomInfoProxyProtocol, Never>(RoomInfoProxyMock(configuration))
let roomProxyMock = JoinedRoomProxyMock(configuration)
@@ -839,11 +795,11 @@ struct RoomDetailsScreenViewModelTests {
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: notificationSettingsProxyMock,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
let deferredShared = deferFulfillment(context.observe(\.viewState),
message: "The pill should be shown for rooms with shared history visibility") { state in