metova / MetovaTestKit

A collection of useful test helpers designed to ease the burden of writing tests for iOS applications.
MIT License
23 stars 2 forks source link

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


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.