0

I just started writing tests using XCTest, I have to write test cases to test if the right analytics event is passed, the analytics system has enum based events something like this.

enum AnalyticsEvent {
    case loginScreenViewed
    case loginAttempted
    case loginFailed(reason: LoginFailureReason)
}

Analytics Engine

protocol AnalyticsEngine: class {
    func sendAnalyticsEvent(named name: String, metadata: [String : String])
}

Implementation

class AnalyticsManager {
    private let engine: AnalyticsEngine

    init(engine: AnalyticsEngine) {
        self.engine = engine
    }

    func log(_ event: AnalyticsEvent) {
        engine.sendAnalyticsEvent(named: event.name, metadata: event.metadata)
    }
}
class someViewModel{
    private let analytics: AnalyticsManager!

    func viewLoaded() {
        analytics.screenView(name: .loginScreenViewed)
    }
}
class someViewController {
    private var viewModel : someViewModel?
    
    func viewDidLoad(){
       //fires an event
       viewModel.viewDidLoad() 
    } 

I checked a few articles which mentioned I have a mock class and store the event fired in an event queue array and assert the event against the array.

A basic assertion would be as follows (here eventQueue is an array where we are storing our mock events)

let event = try XCTUnwrap(analytics.eventQueue.first)
XCTAssertEqual(event.key,AnalyticsEvent.loginScreenViewed)

I would like to understand is there a way to standardize this kind of assertion, how do I dynamically check which analytics event the mock event has to be asserted against and if the respective properties are set for the event and also how to go about UI testing for the same.

marilyn
  • 191
  • 3
  • 3
  • 17

1 Answers1

0

We can handle Unit tests following way:

Implementation Logic

typealias LoginFailureReason = String // You can remove it as required 

enum AnalyticsEvent: Equatable {
    case loginScreenViewed
    case loginAttempted
    case loginFailed(reason: LoginFailureReason)

    var name: String {
        // add event name for each case
        return "mock"
    }

    var metadata: [String: String] {
        // add metadata for each case
        return [:]
    }
}

protocol AnalyticsManagerInterface: AnyObject {
    func log(_ event: AnalyticsEvent)
    func screenView(name event: AnalyticsEvent)
}

final class AnalyticsManager: AnalyticsManagerInterface {
    private let engine: AnalyticsEngine

    init(engine: AnalyticsEngine = AnalyticsEngineHandler()) {
        self.engine = engine
    }

    func log(_ event: AnalyticsEvent) {
        engine.sendAnalyticsEvent(named: event.name, metadata: event.metadata)
    }

    func screenView(name event: AnalyticsEvent) {
        engine.sendAnalyticsEvent(named: event.name, metadata: event.metadata)
    }
}

protocol AnalyticsEngine: AnyObject {
    func sendAnalyticsEvent(named name: String, metadata: [String: String])
}

final class AnalyticsEngineHandler: AnalyticsEngine {
    func sendAnalyticsEvent(named name: String, metadata: [String: String]) {
        // Make analytics call
    }
}

final class SomeViewModel {
    private let analyticsManager: AnalyticsManagerInterface!
    init(analyticsManager: AnalyticsManagerInterface = AnalyticsManager()) {
        self.analyticsManager = analyticsManager
    }

    func viewLoaded() {
        analyticsManager.screenView(name: .loginScreenViewed)
    }
}

Mock class:

typealias AnalyticsEventCallBack = ((AnalyticsEvent) -> Void)

final class MockAnalyticsManager: AnalyticsManagerInterface {
    var didLogEvent: AnalyticsEventCallBack?

    // MARK: - Analytics

    func log(_ event: AnalyticsEvent) {
        didLogEvent?(event)
    }

    func screenView(name event: AnalyticsEvent) {
        didLogEvent?(event)
    }
}

typealias AnalyticsEngineResult = (name: String, metadata: [String: String])
typealias AnalyticsEngineCallBack = ((AnalyticsEngineResult) -> Void)

final class MockAnalyticsEngine: AnalyticsEngine {
    var didLogEvent: AnalyticsEngineCallBack?

    // MARK: - Analytics

    func sendAnalyticsEvent(named name: String, metadata: [String: String]) {
        didLogEvent?((name, metadata))
    }
}

Unit test:

import XCTest
@testable import your_target

final class SomeViewModelTests: XCTestCase {
    private var testViewModel: SomeViewModel!
    private var mockAnalyticsManager: MockAnalyticsManager!

    override func setUp() {
        mockAnalyticsManager = MockAnalyticsManager()
        testViewModel = SomeViewModel(analyticsManager: mockAnalyticsManager)
    }

    override func tearDown() {}

    func testViewLoaded() {
        let expectation = self.expectation(description: #function)
        var testEvent: AnalyticsEvent!
        self.mockAnalyticsManager.didLogEvent = { result in
            testEvent = result
            expectation.fulfill()
        }
        testViewModel.viewLoaded()
        waitForExpectations(timeout: 2, handler: nil)
        XCTAssertEqual(testEvent, AnalyticsEvent.loginScreenViewed)
    }
}


final class AnalyticsManagerTests: XCTestCase {
    private var testViewModel: AnalyticsManager!
    private var mockAnalyticsEngine: MockAnalyticsEngine!

    override func setUp() {
        mockAnalyticsEngine = MockAnalyticsEngine()
        testViewModel = AnalyticsManager(engine: mockAnalyticsEngine)
    }

    override func tearDown() {}

    func testlogEvent() {
        let expectation = self.expectation(description: #function)
        var testAnalyticsEngineResult: AnalyticsEngineResult!
        self.mockAnalyticsEngine.didLogEvent = { result in
            testAnalyticsEngineResult = result
            expectation.fulfill()
        }
        testViewModel.log(.loginAttempted)
        waitForExpectations(timeout: 2, handler: nil)
        XCTAssertEqual(testAnalyticsEngineResult.name, "mock")
        XCTAssertEqual(testAnalyticsEngineResult.metadata, [:])
    }

    func testScreenView() {
        let expectation = self.expectation(description: #function)
        var testAnalyticsEngineResult: AnalyticsEngineResult!
        self.mockAnalyticsEngine.didLogEvent = { result in
            testAnalyticsEngineResult = result
            expectation.fulfill()
        }
        testViewModel.screenView(name: .loginScreenViewed)
        waitForExpectations(timeout: 2, handler: nil)
        XCTAssertEqual(testAnalyticsEngineResult.name, "mock")
        XCTAssertEqual(testAnalyticsEngineResult.metadata, [:])
    }
}
Nischal Hada
  • 3,230
  • 3
  • 27
  • 57