A collection of useful test helpers designed to ease the burden of writing tests for iOS applications.

Overview

MetovaTestKit

Build Status CocoaPods Compatible Documentation Coverage Status Platform Twitter

MetovaTestKit is a collection of useful test helpers designed to ease the burden of writing tests for iOS applications.


Requirements

  • Swift 4.0
  • iOS 9+

Installation

MetovaTestKit is available through CocoaPods.

MetovaTestKit is intended to be used with unit testing targets. To install it, add MetovaTestKit your project's Podfile:

target 'YourApp' do
  # Your app's pods:
  pod 'DataManager'
  pod 'ThunderCats'
  pod 'MetovaBase'

  target 'YourAppTests' do
    inherit! :search_paths
    pod 'MetovaTestKit'
  end
end

And run pod install

If you would like to test a beta version of MetovaTestKit, you can install the latest from develop:

pod 'MetovaTestKit', :git => 'https://github.com/metova/MetovaTestKit.git', :branch => 'develop'

Usage

MTKTestable

MetovaTestKit defines the MTKTestable protocol. Correct implementation of this protocol allows for functional unit testing. It abstracts away the set up and tear down code into extensions of the types you want to test, and allows for functional unit tests.

func testOutlets() {
    HomeViewControllerClass.test { testVC in
        XCTAssertNotNil(testVC.userameTextField)
        XCTAssertNotNil(testVC.passwordTextField)
        XCTAssertNotNil(testVC.loginButton)
    }
}

The test function rethrows any errors thrown inside the testBlock, allowing you to leveraging throwing test cases to more conveniently denote failures.

func testLogin() throws {
    try HomeViewControllerClass.test { testVC in
        try testVC.login(username: "jimmythecorgi", password: "woofwoof123")
    }
}

Testing UIKit Components

UIAlertController

Verify that a view controller presented an alert having a particular style, title, message, and actions.

MTKAssertAlertIsPresented(
    by: testVC,
    style: .alert,
    title: "Warning",
    message: "Are you sure you want to delete this user?",
    actions: [
        ExpectedAlertAction(title: "Delete", style: .destructive),
        ExpectedAlertAction(title: "Cancel", style: .cancel)
    ]
)

UIBarButtonItem

Verify that a bar button item has the expected target/action pair and that the target actually responds to the selector that will be sent to it.

MTKAssertBarButtonItem(testVC.editBarButtonItem, sends: #selector(MyViewController.didTapEditButton(_:)), to: testVC) 

UIControl

With a single assertion, you can verify that your control actions are hooked up and that your target actually responds to the selector that will be sent to it.

MTKAssertControl(testVC.loginButton, sends: #selector(LoginViewController.didTapLoginButton(_:)), to: testVC, for: .touchUpInside, "The login button should be hooked up to the login action.") 

UICollectionViewCell

Assert that a collection view returns a cell of a specific type for a given index path. Pass a block of code to perform additional tests on the cell, if it exists.

MTKTestCell(in: collectionView, at: indexPath, as: MyCollectionViewCell.self) { testCell in 
    XCTAssertEqual(testCell.label.text, "Hello World!")
}

See the tests for examples of test failures this method can generate.

UITableViewCell

Assert that a table view returns a cell of a specific type for a given index path. Pass a block of code to perform additional tests on the cell, if it exists.

MTKTestCell(in: tableView, at: indexPath, as: MyTableViewCell.self) { testCell in
    XCTAssertEqual(testCell.label.text, "Hello World!")
}

See the tests for examples of test failures this method can generate.

UISegmentedControl

Verify that a UISegmentedControl has the segment titles you are expecting.

MTKAssertSegmentedControl(segmentedControl, hasSegmentTitles: ["Followers", "Following"])

Testing Dates

You can use MetovaTestKit to assert two dates are equal when only considering specified components.

MTKAssertEqualDates(date1, date2, comparing: .year, .month, .day)

This assertion accepts components as a variadic argument or as a Set<Calendar.Component>.

let components: Set<Calendar.Component> = [.year, .month, .day, .hour, .minute, .second]
MTKAssertEqualDates(date1, date2, comparing: components)
MTKAssertEqualDates(date3, date4, comparing: components)
MTKAssertEqualDates(date5, date6, comparing: components)

Testing Auto Layout Constraints

You can use MetovaTestKit to assert that you do not have broken Auto Layout constraints.

MTKAssertNoBrokenConstraints {
    // code that does anything that impacts the user interface
    // including simply loading a view for the first time
}

This assertion will fail for any broken constraints and report the number of constraints that broke during the test. You can also pass a custom message.

MTKAssertNoBrokenConstraints(message: "Constraints were broken.") {
    // code to test
}

This test also returns a value with a count of the number of constraints broken.

let brokenConstraintCount = MTKAssertNoBrokenConstraints {
    // code to test
}

Testing Exceptions

You can use MetovaTestKit to assert that code that should not throw exceptions doesn't. Without MetovaTestKit, this would result in the entire test suite crashing. With MetovaTestKit, this is just a failed test, and you still get to run the rest of the test suite.

MTKAssertNoException {
    // code that should not throw exceptions
    // results in passing test if no exceptions are thrown
    // results in failing test if exceptions are thrown
}

You can also pass a message to print on failure.

MTKAssertNoException(message: "Exception was thrown.") {
    // code that should not throw exceptions
    // results in passing test if no exceptions are thrown
    // results in failing test if exceptions are thrown
}

You can also test code to verify that exceptions are thrown, and can do this without crashing your test suite. If you do not care about the specific exception but only want to verify that the code block throws an exception, you can use MTKAssertException:

MTKAssertException {
    // code that should throw exceptions
    // results in passing test if an exception is thrown
    // results in a failing test if this closure returns without throwing
}

Like MTKAssertNoException, this function also accepts a message:

MTKAssertException(message: "No exception was thrown.") {
    // code that should throw exceptions
    // results in passing test if an exception is thrown
    // results in a failing test if this closure returns without throwing
}

These methods do return the thrown exception in case you need more information about it.

guard let exception = MTKAssertException(testBlock: throwingBlock) else {
    XCTFail("Block failed to throw an exception")
    return
}

// More assertion about the given exception that was returned
if let exception = MTKAssertNoException(testBlock: blockThatShouldntThrow) {
    XCTFail("Block should not have thrown but instead threw \(exception)")
    return
}

If the closure did not throw an exception, the function returns nil. Otherwise, it returns an instance of NSException which you can verify is the exception you expected your block to throw.

Asynchronous Testing

XCTest provides asynchronous testing capabilities using expectation(description:) and waitForExpectations(timeout:handler:). However, when testing simple delayed asynchronous actions, this approach can be cumbersome and the intent might not be immediately obvious. Using MetovaTestKit's MTKWaitThenContinueTest(after:) utility method, these kinds of tests become simple and they read naturally.

mockUserSearchNetworkRequest(withResponseTime: 0.5)
testViewController.didTapSearchButton()
 
XCTAssertFalse(testViewController.searchButton.isEnabled, "The search button should be disabled while a search request is taking place.")
 
MTKWaitThenContinueTest(after: 1)
 
XCTAssertTrue(testViewController.searchButton.isEnabled, "Once the request is complete, the search button should be re-enabled.") 

Documentation

Documentation can be found here.


Credits

MetovaTestKit is owned and maintained by Metova Inc.

Contributors

If you would like to contribute to MetovaTestKit, see our CONTRIBUTING guidelines.

MetovaTestKit banner image and other assets provided by Christi Johnson.


License

MetovaTestKit is available under the MIT license. See the LICENSE file for more info.

Comments
  • Deprecate

    Deprecate "MTK" in favor of "MetovaTestKit"

    The name of this repository is "MetovaTestKit", the name of the CocoaPod is "MTK", and the README references the names "Metova Test Kit" and "MTK". This leads to a bit of confusion. Furthermore, "MTK" isn't very descriptive. It's analogous to a bad variable name.

    In light of this, I'd like to deprecate "MTK" in favor of "MetovaTestKit". I don't think it's necessary for us to send a PR to CocoaPods' master spec repo—and we wouldn't want to since that would break existing Podfiles that reference MTK. Instead, I believe we can simply rename our podspec, push the new podspec, then run:

    pod trunk deprecate MTK --in-favor-of=MetovaTestKit
    

    However, I don't want to just rename MTK. I'd like to make a number of breaking changes to make the framework look and feel like a Swift framework rather than an Objective-C framework—after all, it's written in Swift. So, I think we should drop the "MTK" prefix from all assertions, and name them with lowerCamelCase as per the Swift API Design Guidelines.

    I believe we originally went with the all caps "MTK" prefix in order to mirror the naming conventions of the XCTest assertions, but I don't think there's really any value in doing that. After all, all the new methods that have been added to XCTest no longer follow that convention (e.g. expectation(description:)).

    The practice of prefixing class/method names with initials came from Objective-C where it was necessary in order to avoid naming conflicts. However, with Swift this isn't a problem. Even if a project was importing MetovaTestKit along with another framework that also happened to have an extension on XCTestCase with a method with the same name as one from MetovaTestKit (e.g. assertControl(_:sends:to:for:)), the naming conflict can be resolved by fully qualifying it with the name of the module:

    MetovaTestKit.assertControl(testVC.myControl, sends: #selector(MyViewController.didTapControl(_:)), to: testVC, for: .touchUpInside)
    

    Any thoughts or concerns?

    discussion 
    opened by lgauthier 8
  • Feature/alert controller assertion

    Feature/alert controller assertion

    @nhgrif This adds an assertion for testing that a view controller presented a UIAlertController with a particular style, title, message, and actions. Just so you're aware, in order to test this assertion, I had to be able to successfully present an alert which isn't possible without a window. So, I added a new test target that actually runs an empty UIKit app so I have a window to add my presenter view controller to for the tests.

    opened by lgauthier 8
  • Add the ability for MTKTestable test(_:) blocks to throw

    Add the ability for MTKTestable test(_:) blocks to throw

    XCTest allows test methods to denote a test failure by marking the test as throwing. This makes the testing of throwing functions a tad simpler.

    func testThrowingFunctionSucceeds() throws {
        try doSomethingThatShouldNotFail()
    }
    

    This tends to be much more elegant than something like this:

    func testThrowingFunctionSucceeds() {
        do {
            try doSomethingThatShouldNotFail()
        }
        catch {
            XCFail("An error occurred: \(error)")
        }
    }
    

    In the same light, the test(_:) function could be much more ergonomic by allowing uncaught errors to fail a test via propagation.

    func testThrowingMethodSucceeds() throws {
        try Object.test { instance in
            try instance.doSomethingThatShouldNotFail()
        }
    }
    

    The test(_:) function was marked rethrows to allow the user to forego this functionality while testing functions that don't throw. This prevents the cruft of having to try non-throwing functionality within a test when unnecessary.

    func testNonThrowingMethod() {
        Object.test { instance in
            instance.doSomethingThatCannotFail()
            
            // Make assertions below...
        }
    }
    

    This PR is source breaking – anything that is already using the test(_:) method will need to add the throws and rethrows keywords. This should be a minimal change, all of which can be caught by the compiler due to a Type does not conform to protocol 'MTKTestable' compiler error.

    opened by schrismartin 2
  • `MTKWaitThenContinueTest(after:)` should be updated so that it doesn't use a closure

    `MTKWaitThenContinueTest(after:)` should be updated so that it doesn't use a closure

    @nhgrif I kind of forgot about this for a while. Basically, the day after releasing version 1.1.0 which included the MTKWaitThenContinueTest(after:) function, @schrismartin made a keen observation which makes me want to deprecate this original version and add a new version that takes no closure.

    Usage for the original version looks like this:

    // Perform some async action
    MTKWaitThenContinueTest(after: 1) {
        // Perform assertions
    }
    

    What Chris pointed out is that this works just the same because it's a blocking function:

    // Perform some async action
    MTKWaitThenContinueTest(after: 1) {}
    // Perform assertions
    

    I think the benefit of removing the closure is that it simplifies the interface and we'd be able to remove these warnings in the function's docs:

        /// - Warning: This method works by creating an expectation and calling `waitForExpectations(timeout:handler:)`. Because of this, the following actions will result in a failure:
        ///   - Creating nested calls to `MTKWaitThenContinueTest(after:on:testAction:)`.
        ///   - Creating expectations within `testAction` that are not fulfilled within `testAction`
        ///   - Calling `waitForExpectations(timeout:handler:)` within `testAction`.
    

    Technically, we'd still want to include a warning to explain that you won't be able to create an expectation that isn't fulfilled prior to calling the method, but we'll be able to remove that warning for iOS 11 since the expectation API has been updated to allow us to wait only for a specific expectation.

    Of course, because we've already released version 1.1.0, we can't just remove the closure argument because it would break backwards compatibility. One backwards compatible option would be to make the closure optional and add a default value of nil. However, this strategy won't allow us to add a deprecation annotation on the function. So, I'm proposing that we leave the original function as is and add a deprecation annotation. Then add a new function that's identical but doesn't have the closure argument or the DispatchQueue argument.

    Thoughts?

    opened by lgauthier 2
  • Test cases for failing tests

    Test cases for failing tests

    As noted in a pair of TODO comments in ExceptionTestingTests.swift, we do not currently have way of testing that functions should mark a test as failing actual do that without failing the MTK test itself.

    opened by nhgrif 2
  • Create replacement for CocoaDocs

    Create replacement for CocoaDocs

    Ever since the end of May this year, CocoaDocs no longer generates and hosts documentation for newly submitted pod versions. Read more here.

    We need to figure out a simple way of generating and hosting documentation for new versions ourselves. Generating the documentation is fairly simple--we can just use jazzy, which is the tool CocoaDocs used. The trickier parts that we need to address are:

    • Automate documentation generation each time we submit a new version.
    • Hosting the documentation ourselves.

    I've looked into it a little bit so far, and I think we could potentially simply host our docs within our GitHub repo to make publishing new docs easy, but we'd want to automate this process. You can check out the branch I've started to see what I've done so far:

    So far, the main problems with what I've done on that branch are:

    • We have to remember to run jazzy each time before submitting a new version.
    • We have to remember to update the documentation link in the README so it references the index page of the latest docset.
    • The documentation badge isn't working on the README. This may be a simple fix though.
    help wanted 
    opened by lgauthier 1
  • Modernize spacing after a period.

    Modernize spacing after a period.

    @nhgrif I had to do this because it was driving me nuts.

    https://www.cultofpedagogy.com/two-spaces-after-period/ http://www.quickanddirtytips.com/education/grammar/two-spaces-after-a-period http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html

    😬

    opened by lgauthier 1
  • Feature/testing failures

    Feature/testing failures

    We can now write unit tests to verify that our custom assertion functions actually fail tests.

    This PR resolves https://github.com/metova/MetovaTestKit/issues/2

    opened by nhgrif 1
  • Test suite enhancements

    Test suite enhancements

    The primary motivation behind this PR is to update the way test failures are checked.

    When adding the main target assertion for comparing dates, I ran into a problem where the order of the sub-parts of the failure message could not be guaranteed due to the use of a set. Yet the way we were checking for assertions was via a direct string comparison.

    In order to deal with this, the TestFailureExpectation struct was turned into a protocol. This protocol defines three optional functions. They default to returning TestVerificationResult.expected, basically ignore that specific check.

    A BasicTestFailureExpectation class was also added in order to retain the exist test use-cases.

    Ultimately though, this change allows the addition of the DetailedTestFailureExpectation (which may need a better name?), which allows to simply check that the description of the failure contains certain substrings (in any particular order).

    This then allowed for the update of the date tests to assert that all of the mismatched components are reported in the case of more than one mismatched component, which is what we want (and could not previously test consistently, due to non-guaranteed order of items in a set).

    This protocol-oriented approach to the test failure expectations should be relatively future-proofed against unforeseen problems like what was run into while testing the date assertions.

    I also updated hardcoded line numbers in several spots to be simply offsets from the current #line, which should decrease the likelihood that these tests just start failing (despite an easy fix even if they did) simply due to someone adding or subtracting some code further up the file.

    opened by nhgrif 0
  • MTKAssertThrowsError & MTKAssertNoThrow

    MTKAssertThrowsError & MTKAssertNoThrow

    MetovaTestKit already contains two assertions for dealing with Objective-C level exceptions, as documented here.

    Should we include assertions for dealing with Swift level errors? I understand that both XCTAssertThrowsError and XCTAssertNoThrow exist, and I generally prefer to not rewrite Apple code, however I have two issues with both of these methods.

    First, the signature for the Xcode Test functions is arguably confusing or misleading. We generally prefer closure arguments to be last so we can leverage Swift's nice trailing closure syntax, as we did with Exception Testing Functions. The Xcode Test default functions have the closure to be tested as the first argument. This is done presumably because all of the following arguments are optional (and only used in very rare occasion... they're for people doing things like what we're doing with MTK) and to be consistent with every other function in the Xcode Test suite, which is fair enough.

    Second, and arguably more importantly, the Xcode Test functions do not allow you to have much information about the error that was thrown (other than what was printed), nor does it allow you to make further assertions about the thrown error. The first problem is relatively minor, but the second problem can potentially get in the way of writing a complete test suite, particularly in the case where you want to assert that an error was thrown. You certainly want to be sure the error was of the type you were expecting and it contained all the information you expected to, otherwise you might have false positive results. If we were to implement these assertions in MTK, we would return any thrown error we encountered, just as we do with Objective-C exceptions we catch in the exception testing methods.

    However, I believe everything else in MTK is purely supplemental functions. These two additions would mark the first replacements for existing XCT functionality.

    discussion 
    opened by nhgrif 2
  • Clean up documentation / readme

    Clean up documentation / readme

    As this library grows, trying to document everything in the top level README file will become overbearing. The top-level README should be trimmed down to the following major sections: Requirements, Installation, Usage, Credits, License.

    The Usage section itself can contain a link to the full set of documentation generated based on doc comments. It should also have a very limited set of actual code snippets demonstrating the usage of the library. For this, we should determine what are the most popular or appealing aspects of the library and document these there. This section should contain some prose about who this library is meant for, what you can accomplish with it, etc. Then, this section should also link out to more complete & detailed documentation, including multiple example code snippets for each chunk of the total library. Consider Alamofire's Documentation directory as a guideline toward the direction we should be looking at. However, I imagine each chunk of MetovaTestKit having its own markdown file with documentation. The top-level README can then link straight to each of these individual files.

    documentation 
    opened by nhgrif 0
  • Add tool to verify that test files are added to the test target

    Add tool to verify that test files are added to the test target

    I'd like to create some kind of tool that can verify that all the test files are added to the test target. It's not uncommon for a test file to get removed from a test target during a .xcodeproj merge conflict resolution. If that happens, entire test classes can ended up being abandoned and forgotten about for a long time.

    We'll need to spend some time designing how we'd want this to work, but I'm thinking that no matter how we end up designing it, it's going to have to be some kind of command line tool that can be run as a run script build phase.

    feature 
    opened by lgauthier 1
  • Add image comparison assertions

    Add image comparison assertions

    Although iOS seems to compare image assets correctly if they are loaded using the imageNamed function on UIImage, this only works because iOS uses the same image in memory for those objects. If the same image is loaded from another source (from the file system, using SDWebImage, etc.), this comparison will not work. To avoid this confusing scenario, an assertion should be added that compares the image data so we can know that two images are identical.

    feature 
    opened by wibs 1
  • Enhanced assertion failure messages

    Enhanced assertion failure messages

    Currently, when an MTK assertion fails, the message Xcode generates always includes "XCTAssertTrue failed - " plus the message or our default message. If possible, we'd like this message to instead use the name of function out of MTK.

    screen shot 2016-05-07 at 9 52 59 pm help wanted 
    opened by nhgrif 0
Owner
null
Swift framework containing a set of helpful XCTest extensions for writing UI automation tests

AutoMate • AppBuddy • Templates • ModelGenie AutoMate AutoMate is a Swift framework containing a set of helpful XCTest extensions for writing UI autom

PGS Software 274 Dec 30, 2022
Detailed explanations and implementations of various maths concepts for writing high performance code/algorithms backed with Unit tests.

Detailed explanations and implementations of various maths concepts which can help software Engineers write high performance code/algorithms backed with Unit tests.

Mussa Charles 2 Sep 25, 2022
XCTestExtensions is a Swift extension that provides convenient assertions for writing Unit Test.

XCTestExtensions Features XCTAssertEventually (that convenient assertions for writing Unit Test). Use "XCTAssertEventually", you can write asynchronou

shindyu 22 Dec 1, 2022
Write unit tests which test the layout of a view in multiple configurations

Overview This library enables you to write unit tests which test the layout of a view in multiple configurations. It tests the view with different dat

LinkedIn 565 Nov 16, 2022
Kfm-ios-test - Test online for iOS Developer position in Kimia Farma or PT. Buana Varia Komputama

kfm-ios-test Kimia Farma Mobile iOS Test Test online for iOS Developer position

Erwindo Sianipar 3 Feb 23, 2022
Test-To-Do-List - Test To Do List with core data

test-To-Do-List This is my first pet project with core data Launch screen Main s

Artem 0 Feb 26, 2022
An elegant library for stubbing HTTP requests with ease in Swift

Mockingjay An elegant library for stubbing HTTP requests in Swift, allowing you to stub any HTTP/HTTPS using NSURLConnection or NSURLSession. That inc

Kyle Fuller 1.5k Dec 3, 2022
Swift Package with examples of how to tests iOS apps

GimmeTheCodeTDD A swift package with examples of unit tests in iOS development. Requirements Xcode 13 Content Dependency Injection Constructor Injecti

Dominik Hauser 8 Oct 11, 2021
Library for unifying the approach to network mocking in iOS unit- & UI-tests.

TinkoffMockStrapping Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Installati

Online financial ecosystem 22 Jan 3, 2023
Bluepill is a reliable iOS testing tool that runs UI tests using multiple simulators on a single machine

Bluepill is a tool to run iOS tests in parallel using multiple simulators. Motivation LinkedIn created Bluepill to run its large iOS test suite in a r

Mobile Native Foundation 3.1k Jan 3, 2023
Upload failing iOS snapshot tests cases to S3

Second Curtain If you're using the cool FBSnapshotTestCase to test your iOS view logic, awesome! Even better if you have continuous integration, like

Ash Furrow 129 Sep 6, 2022
Snapshot view unit tests for iOS

iOSSnapshotTestCase (previously FBSnapshotTestCase) What it does A "snapshot test case" takes a configured UIView or CALayer and uses the necessary UI

Uber Open Source 1.7k Jan 4, 2023
TestSchedulerDemo - Demonstration code for iOS Unit Tests and Asynchronous

TestSchedulerDemo This repository contains demonstration code for my Medium arti

Carsten Wenderdel 0 Mar 19, 2022
Erik is an headless browser based on WebKit. An headless browser allow to run functional tests, to access and manipulate webpages using javascript.

Erik Erik is a headless browser based on WebKit and HTML parser Kanna. An headless browser allow to run functional tests, to access and manipulate web

Eric Marchand 544 Dec 30, 2022
Mockit is a Tasty mocking framework for unit tests in Swift 5.0

Mockit Introduction Mockit is a Tasty mocking framework for unit tests in Swift 5.0. It's at an early stage of development, but its current features a

Syed Sabir Salman-Al-Musawi 118 Oct 17, 2022
Trying to implement Unit Tests for @Binding properties in a ViewModel

BindingTester Trying to implement Unit Tests for @Binding properties in a ViewModel ViewModel to be tested class SheetViewModel: ObservableObject {

Raphael Guye 0 Oct 22, 2021
Catching fatal errors in unit tests

Precondition Catching When running tests which hit fatal errors, often preconditions the built-in support with XCTest. One package which supports cach

Brennan Stehling 0 Nov 28, 2021
Small library to easily run your tests directly within a Playground

[] (https://developer.apple.com/swift/) Build Status Branch Status master develop About PlaygroundTDD enables you to use TDD directly on Xcode Playgro

Whiskerz AB 317 Nov 22, 2022
UITest helper library for creating readable and maintainable tests

UITestHelper General information When creating UI tests you will see that you are often repeating the same pieces of code. The UITestHelper library wi

Edwin Vermeer 56 Feb 20, 2022