Scott H
Scott H

Reputation: 1529

Unit testing strategy for conforming to Equatable

Let's say I have a struct that conforms to Equatable for my model, something like this:

struct Model: Equatable {
    var a: Int = 0
    var b: String = ""
}

func ==(lhs: Model, rhs: Model) -> Bool {
    return lhs.a == rhs.a && lhs.b == rhs.b
}

Now I write some unit tests for this. Something like:

func testModelsAreEqual() {
    let model1 = Model()
    let model2 = Model()
    XCTAssertEqual(model1, model2)
}

func testModelsAreNotEqual1() {
    let model1 = Model()
    var model2 = Model()
    model2.b = "hello world"
    XCTAssertNotEqual(model1, model2)
}

func testModelsAreNotEqual2() {
    let model1 = Model()
    var model2 = Model()
    model2.a = 1
    XCTAssertNotEqual(model1, model2)
}

But how can I write a test that will protect me from the scenario that another property is added to Model without being added to the memberwise-equality check for == like:

struct Model: Equatable {
    var a: Int = 0
    var b: String = ""
    var c: Double = 0
}

func ==(lhs: Model, rhs: Model) -> Bool {
    return lhs.a == rhs.a && lhs.b == rhs.b
}

Where obviously my tests will all still pass even though conceptually my Equatable is broken. Is there a testing strategy I could adopt here that will help alert me to this problem? Is there something I can do with Swift's Mirror and limited reflection? Perhaps Mirror.children.count? Or does anyone have a better suggestion?

Upvotes: 1

Views: 706

Answers (2)

Code Different
Code Different

Reputation: 93161

This is my take:

func testModel3() {
    let model1 = Model()
    var model2 = Model()
    model2.c = 1
    model2.p.name = "Jack"

    let mirror1 = Mirror(reflecting: model1)
    let mirror2 = Mirror(reflecting: model2)

    let prop1 = mirror1.children.reduce([String: NSObject]()) {
        (var dict, e) in
        dict[e.label!] = e.value as? NSObject
        return dict
    }
    let prop2 = mirror2.children.reduce([String: NSObject]()) {
        (var dict, e) in
        dict[e.label!] = e.value as? NSObject
        return dict
    }

    if model1 == model2 {
        XCTAssertEqual(prop1, prop2, "Not really equal")
    } else {
        XCTAssertNotEqual(prop1, prop2, "Not really diffrent")
    }
}

It builds 2 dictionaries with the names & values of all properties in model1 and model2, then assert if these 2 dictionaries are equal.

Big warning: I have not thoroughly tested this. It worked for the limited number of cases I threw at it. You may have to improvise from here.

Upvotes: 0

Scott H
Scott H

Reputation: 1529

After searching for other solutions, I have decided to use reflection for member count to alert me of changes. Here is the test for the example above:

func testModelStillHas2Members() {
    XCTAssertEqual(Mirror(reflecting: Model()).children.count, 2, "The member count of Model has changed, please check the `==` implementation to ensure all members are accounted for.")
}

Upvotes: 3

Related Questions