Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing multiple methods calling order

I have 3 methods that are related to a specific class which is defined as follows:

class MyClass: NSObject {
    func myMethod() {
        methodA()
        methodB()
        methodC()
    }
    func methodA() {}
    func methodB() {}
    func methodC() {}
}

I need to test that myMethod has called all 3 methods by the order they are implemented: methodA then methodB then methodC to be tested with XCode Unit Tests, regardless of the implementation of these methods, I have created a subclass in the test case that looks like the following:

class ChildClass: MyClass {
    var method1CallingDate: Date?
    var method2CallingDate: Date?
    var method3CallingDate: Date?

    override func methodA() {
        super.methodA()
        method1CallingDate = Date()
    }
    override func methodB() {
        super.methodB()
        method2CallingDate = Date()
    }
    override func methodC() {
        super.methodC()
        method3CallingDate = Date()
    }
}

Now in the test method, I start by calling those 3 methods, then I assert that all three dates are not nil first, then compare them like this:

XCTAssertLessThan(method1CallingDate, method2CallingDate)
XCTAssertLessThan(method2CallingDate, method3CallingDate)

The problem I ran into was that the test sometimes succeeds and sometimes fails, i guess due to Date object is (randomly) the same between 2 of the method calls. Is there a better way to test the order of calling multiple methods ?

p.s. this is easily done in the Android SDK org.mockito.Mockito.inOrder

like image 784
JAHelia Avatar asked May 13 '26 15:05

JAHelia


2 Answers

First, make a mock object that records the order. No dates, no strings. Just an enumeration.

class MockMyClass: MyClass {
    enum invocation {
        case methodA
        case methodB
        case methodC
    }
    private var invocations: [invocation] = []

    override func methodA() {
        invocations.append(.methodA)
    }

    override func methodB() {
        invocations.append(.methodB)
    }

    override func methodC() {
        invocations.append(.methodC)
    }

    func verify(expectedInvocations: [invocation], file: StaticString = #file, line: UInt = #line) {
        if invocations != expectedInvocations {
            XCTFail("Expected \(expectedInvocations), but got \(invocations)", file: file, line: line)
        }
    }
}

Then in the test:

mock.verify(expectedInvocations: [.methodA, .methodB, .methodC])

No async waiting. Simple to call. Clear failure messages.

like image 169
Jon Reid Avatar answered May 15 '26 07:05

Jon Reid


You could do something like this using a String to keep track of the order:

class ChildClass: MyClass {
    var order = ""

    override func methodA() {
        super.methodA()
        order = String((order + "A").suffix(3))
    }
    override func methodB() {
        super.methodB()
        order = String((order + "B").suffix(3))
    }
    override func methodC() {
        super.methodC()
        order = String((order + "C").suffix(3))
    }
}

Then, just check that order is "ABC".


Or, if it is valid to call B multiple times between A and C:

class ChildClass: MyClass {
    var order = ""

    override func methodA() {
        super.methodA()
        order = order.replacingOccurrences(of: "A", with: "") + "A"
    }
    override func methodB() {
        super.methodB()
        order = order.replacingOccurrences(of: "B", with: "") + "B"
    }
    override func methodC() {
        super.methodC()
        order = order.replacingOccurrences(of: "C", with: "") + "C"
    }
}

Example:

let c = ChildClass()
c.methodA()
c.methodB()
c.methodB()
c.methodC()

print(c.order)
ABC
like image 37
vacawama Avatar answered May 15 '26 06:05

vacawama



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!