'Write test cases to test enum-based analytics system dynamically using XCTest
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.
Solution 1:[1]
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, [:])
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
