Testing animations in UIKit

darrarski

Dariusz Rybicki

Posted on September 2, 2019

Testing animations in UIKit

We take tests seriously at EL Passion. When implementing iOS apps, we always try to practice TDD, pair-programming and regular code-reviews. We want our software to be stable and bug-free. We often work on apps with outstanding design, that includes animations. In this case, we incorporate snapshot-based tests that work not only for static UI but also for animations.

Example

Below you can see a real-life example, taken from E-commerce Today's deals interaction, iOS demo. We implemented custom animation for view controllers transition and tested it using snapshot tests.

Application Test snapshots
Ecommerce app Test snapshots

Animation

Take a look at a simplified example. Consider we have an animated button:

Button animation

It's implemented using native UIKit keyframes animation API:

func animate() {
    UIView.animateKeyframes(
        withDuration: 2,
        delay: 0,
        options: [],
        animations: {
            UIView.addKeyframe(
                withRelativeStartTime: 0,
                relativeDuration: 0.5,
                animations: {
                    self.button.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
                    self.button.backgroundColor = .red
                }
            )
            UIView.addKeyframe(
                withRelativeStartTime: 0.5,
                relativeDuration: 0.5,
                animations: {
                    self.button.transform = .identity
                    self.button.backgroundColor = .blue
                }
            )
        }
    )
}
Enter fullscreen mode Exit fullscreen mode

You can find full source code in ExampleViewController.swift file.

Tests

To test our animated button, we will use SnapshotTesting library from Point-Free.

In our XCTestCase we have to add the animated view to a window:

override func setUp() {
    sut = ExampleViewController()
    window = UIWindow(frame: CGRect(x: 0, y: 0, width: 220, height: 100))
    window.rootViewController = sut
    window.isHidden = false
}
Enter fullscreen mode Exit fullscreen mode

We can then control animation progress using UIViewPropertyAnimator. After setting the desired progress of the animation, we can make a snapshot of the containing window:

func testAnimation() {
    let animator = UIViewPropertyAnimator(duration: 1, curve: .linear, animations: {
        self.sut.animate()
    })

    (0...10).map { CGFloat($0) / 10.0 }.forEach { animationProgress in
        animator.fractionComplete = animationProgress
        assertSnapshot(
            matching: window,
            as: .image(drawHierarchyInKeyWindow: true),
            named: "animation_\(String(format: "%02.0f", animationProgress * 10))"
        )
    }

    animator.pauseAnimation()
    animator.stopAnimation(false)
    animator.finishAnimation(at: .current)
}
Enter fullscreen mode Exit fullscreen mode

This will result in generating 11 snapshot images that our animation will be compared to, every time we run tests:

Button animation snapshots

You can find full source code of presented XCTestCase subclass in ExampleViewControllerTests.swift file.

License

Copyright © 2019 EL Passion

💖 💪 🙅 🚩
darrarski
Dariusz Rybicki

Posted on September 2, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related