0

Can I mock SKPhysicsContact object to feed into -(void)didEndContact:(SKPhysicsContact *)contact method? Or is there any other technique, that can be leveraged here?

class PhysicsTestCase: XCTestCase {

    var physics: GamePhysics!

    ...

    func testCaseOfCollisionsHandling() {

        let contact = SKPhysicsContact()
        contact.bodyA = SKPhysicsBody(circleOfRadius: 10) // Error, 'bodyA' is get-only property

        physics.didEnd(contact) // Physics conforms to `SKPhysicsContactDelegate`
    }

    ...

}

...

// The class that is being tested

class GamePhysics: NSObject, SKPhysicsContactDelegate {

    // MARK: - SKPhysicsContactDelegate

    func didBegin(_ contact: SKPhysicsContact)  {

        guard let nodeA = contact.bodyA.node, let nodeB = contact.bodyB.node else {
            fatalError("No nodes in colliding bodies")
        }

        switch (nodeB, nodeA) {

        case let (ball as LogicalBall, tile as LogicalTile):
           // Performing some logic

        ...

        }
    }

    func didEnd(_ contact: SKPhysicsContact) {

        ...
    }

    ...
}
Zapko
  • 2,461
  • 25
  • 30
  • You could create a small yet project and initialise your objects on top of each other. `didEndContact` should be called in that situation. – Steve Ives May 20 '17 at 12:36
  • What exactly are you trying to do? Why are you trying to set the physics body? This is not something you would ever do. – Steve Ives May 20 '17 at 15:56
  • I want to write a set of tests that checks that my `SKPhysicsContactDelegate` works as expected. – Zapko May 20 '17 at 17:15
  • I've never seen an issue with the `SKPhysicsContact`, so not sure that this needs testing. I have written a 'helper' function that, when called, iterates over all nodes in your scene, examines their physics bodies and `contactTest`/`collision` bit masks and reports on which ones collide or notify upon contacting which others. If this sounds useful, it can be found here - http://stackoverflow.com/documentation/sprite-kit/6261/sknode-collision/24162/simple-sprite-kit-project-showing-collisions-contacts-touch-events#t=201705201740011831274 in the `checkPhysics()` function. – Steve Ives May 20 '17 at 17:41
  • Thanks for the link, but I'm trying to write tests for my class that handles collisions, not that the contactDelegate is called when collision occurs. – Zapko May 20 '17 at 23:59
  • Do you mean tests that handle contacts? No user code is involved in collision handling. – Steve Ives May 21 '17 at 07:30
  • Yeap, that's what I try to test. – Zapko May 21 '17 at 09:58

2 Answers2

2

Although, subclassing proposed by Jon Reid in https://stackoverflow.com/a/44101485/482853 is very neat, I didn't managed to make it work in this particular case because of elusive nature of SKPhysicsContact class itself.

The way this problem can be solved is to use good old Objective C runtime:

func testBallsCollisionIsPassedToHandler() {

    let ballAMock = LogicalBallMock()
    let bodyA = SKPhysicsBody(circleOfRadius: 10)
    bodyA.perform(Selector(("setRepresentedObject:")), with: ballAMock) // So the bodyA.node will return ballAMock

    let ballBMock = LogicalBallMock()
    let bodyB = SKPhysicsBody(circleOfRadius: 10)
    bodyB.perform(Selector(("setRepresentedObject:")), with: ballBMock) // So the bodyB.node will return ballBMock

    let contact = SKPhysicsContact()
    contact.perform(Selector(("setBodyA:")), with: bodyA)
    contact.perform(Selector(("setBodyB:")), with: bodyB)

    physics.didEnd(contact)

    // Assertions ...       

}
Zapko
  • 2,461
  • 25
  • 30
1

When we can't change a type because we don't own the API, the solution is to use the legacy code technique Subclass and Override Method:

class TestablePhysicsContact: SKPhysicsContact {
    var stubbedBodyA: SKPhysicsBody?

    override var bodyA: SKPhysicsBody {
        return stubbedBodyA!
    }
}

To use this in your example test:

    func testCaseOfCollisionsHandling() {
        let contact = TestablePhysicsContact()
        contact.stubbedBodyA = SKPhysicsBody(circleOfRadius: 10)

        physics.didEnd(contact)

        // assert something
    }

For more on this technique, see https://qualitycoding.org/swift-partial-mock/

Jon Reid
  • 20,545
  • 2
  • 64
  • 95
  • Hi Jon, what an honour to get an answer from you : ) I've recently stumbled upon your blog, and got some good ideas from it. Thank you! – Zapko May 26 '17 at 04:31
  • Unfortunately the solution you've proposed doesn't work. Test crashes because `SKPhysicsContact` is an umbrella class and `contact` turns out to be of a type `PKPhysicsContact` in runtime, even though we initiate it as a `TestablePhysicsContact`. – Zapko May 26 '17 at 04:37
  • But thanks for the idea! It's really neat. I'm so into evading subclassing that didn't even thought of this trick. – Zapko May 26 '17 at 04:41
  • You mean it's a class cluster? Ah too bad. – Jon Reid May 27 '17 at 17:12