All Projects → pointfreeco → xctest-dynamic-overlay

pointfreeco / xctest-dynamic-overlay

Licence: MIT license
Define XCTest assertion helpers directly in your application and library code.

Programming Languages

swift
15916 projects
Makefile
30231 projects

Labels

Projects that are alternatives of or similar to xctest-dynamic-overlay

Entwine
Testing tools and utilities for Apple's Combine framework.
Stars: ✭ 306 (+121.74%)
Mutual labels:  xctest
Uitestingexample
Example code from my blog post about UI testing
Stars: ✭ 57 (-58.7%)
Mutual labels:  xctest
Xcfit
Full Stack Protocol Oriented BDD in Xcode for iOS app with Swift using XCUITest, Cucumberish and FitNesse
Stars: ✭ 170 (+23.19%)
Mutual labels:  xctest
Xctesthtmlreport
Xcode-like HTML report for Unit and UI Tests
Stars: ✭ 489 (+254.35%)
Mutual labels:  xctest
Replacer
An easy-to-use library to stub HTTP requests using URLSession and to swizzle methods
Stars: ✭ 30 (-78.26%)
Mutual labels:  xctest
Conbini
Publishers, operators, and subscribers to supplement Combine.
Stars: ✭ 109 (-21.01%)
Mutual labels:  xctest
Hippolyte
HTTP Stubbing in Swift
Stars: ✭ 109 (-21.01%)
Mutual labels:  xctest
Quiz App
A repository reflecting the progress made on the "How to Build iOS Apps with Swift, TDD & Clean Architecture" YouTube series, by Caio & Mike.
Stars: ✭ 230 (+66.67%)
Mutual labels:  xctest
Ocmockito
Mockito for Objective-C: creation, verification and stubbing of mock objects
Stars: ✭ 972 (+604.35%)
Mutual labels:  xctest
Ui Testing Cheat Sheet
How do I test this with UI Testing?
Stars: ✭ 2,039 (+1377.54%)
Mutual labels:  xctest
Ochamcrest
Hamcrest for Objective-C: Powerful, combinable, extensible matchers for verification
Stars: ✭ 701 (+407.97%)
Mutual labels:  xctest
Xctestextensions
XCTestExtensions is a Swift extension that provides convenient assertions for writing Unit Test.
Stars: ✭ 19 (-86.23%)
Mutual labels:  xctest
Bookstore Ios
 Sample iOS App - A collection of examples and patterns for Unit Testing, UI Testing, handling Result/Optionals, writing documentation, and more. Details in README.
Stars: ✭ 147 (+6.52%)
Mutual labels:  xctest
Snap.swift
Snapshot testing in a snap 🎨
Stars: ✭ 420 (+204.35%)
Mutual labels:  xctest
Xctestwd
A Swift implementation of WebDriver server for iOS that runs on Simulator/iOS devices.
Stars: ✭ 195 (+41.3%)
Mutual labels:  xctest
Mockingbird
A convenient mocking framework for Swift
Stars: ✭ 302 (+118.84%)
Mutual labels:  xctest
Viperc
Xcode template for VIPER Architecture for both Objective-C and Swift.
Stars: ✭ 75 (-45.65%)
Mutual labels:  xctest
Automate
Swift framework containing a set of helpful XCTest extensions for writing UI automation tests
Stars: ✭ 244 (+76.81%)
Mutual labels:  xctest
Sbtuitesttunnel
Enable network mocks and more in UI Tests
Stars: ✭ 215 (+55.8%)
Mutual labels:  xctest
Emcee
Emcee is a tool that runs iOS tests in parallel using multiple simulators across many Macs
Stars: ✭ 148 (+7.25%)
Mutual labels:  xctest

XCTest Dynamic Overlay

CI

Define XCTest assertion helpers directly in your application and library code.

Motivation

It is very common to write test support code for libraries and applications. This often comes in the form of little domain-specific functions or helpers that make it easier for users of your code to formulate assertions on behavior.

Currently there are only two options for writing test support code:

  • Put it in a test target, but then you can't access it from multiple other test targets. For whatever reason test targets cannot be imported, and so the test support code will only be available in that one single test target.
  • Create a dedicated test support module that ships just the test-specific code. Then you can import this module into as many test targets as you want, while never letting the module interact with your regular, production code.

Neither of these options is ideal. In the first case you cannot share your test support, and the second case will lead you to a proliferation of modules. For each feature you potentially need 3 modules: MyFeature, MyFeatureTests and MyFeatureTestSupport. SPM makes managing this quite easy, but it's still a burden.

It would be far better if we could ship the test support code right along side or actual library or application code. After all, they are intimately related. You can even fence off the test support code in #if DEBUG ... #endif if you are worried about leaking test code into production.

However, as soon as you add import XCTest to a source file in your application or a library it loads, the target becomes unbuildable:

import XCTest

🛑 ld: warning: Could not find or use auto-linked library 'XCTestSwiftSupport'

🛑 ld: warning: Could not find or use auto-linked framework 'XCTest'

This is due to a confluence of problems, including test header search paths, linker issues, and more. XCTest just doesn't seem to be built to be loaded alongside your application or library code.

Solution

That doesn't mean we can't try! XCTest Dynamic Overlay is a microlibrary that exposes an XCTFail function that can be invoked from anywhere. It dynamically loads XCTest functionality at runtime, which means your code will continue to compile just fine.

import XCTestDynamicOverlay //

Example

A real world example of using this is in our library, the Composable Architecture. That library vends a TestStore type whose purpose is to make it easy to write tests for your application's logic. The TestStore uses XCTFail internally, and so that forces us to move the code to a dedicated test support module. However, due to how SPM works you cannot currently have that module in the same package as the main module, and so we would be forced to extract it to a separate repo. By loading XCTFail dynamically we can keep the code where it belongs.

As another example, let's say you have an analytics dependency that is used all over your application:

struct AnalyticsClient {
  var track: (Event) -> Void

  struct Event: Equatable {
    var name: String
    var properties: [String: String]
  }
}

If you are disciplined about injecting dependencies, you probably have a lot of objects that take an analytics client as an argument (or maybe some other fancy form of DI):

class LoginViewModel: ObservableObject {
  // ...

  init(analytics: AnalyticsClient) {
    // ...
  }

  // ...
}

When testing this view model you will need to provide an analytics client. Typically this means you will construct some kind of "test" analytics client that buffers events into an array, rather than sending live events to a server, so that you can assert on what events were tracked during a test:

func testLogin() {
  var events: [AnalyticsClient.Event] = []
  let viewModel = LoginViewModel(
    analytics: .test { events.append($0) }
  )

  // ...

  XCTAssertEqual(events, [.init(name: "Login Success")])
}

This works really well, and it's a great way to get test coverage on something that is notoriously difficult to test.

However, some tests may not use analytics at all. It would make the test suite stronger if the tests that don't use the client could prove that it's never used. This would mean when new events are tracked you could be instantly notified of which test cases need to be updated.

One way to do this is to create an instance of the AnalyticsClient type that simply performs an XCTFail inside the track endpoint:

import XCTest

extension AnalyticsClient {
  static let testValue = Self(
    track: { _ in XCTFail("\(Self.self).track is unimplemented.") }
  )
}

With this you can write a test that proves analytics are never tracked, and even better you don't have to worry about buffering events into an array anymore:

func testValidation() {
  let viewModel = LoginViewModel(
    analytics: .testValue
  )

  // ...
}

However, you cannot ship this code with the target that defines AnalyticsClient. You either need to extract it out to a test support module (which means AnalyticsClient must also be extracted), or the code must be confined to a test target and thus not shareable.

With XCTest Dynamic Overlay we can have our cake and eat it too 😋. We can define both the client type and the unimplemented test instance right next to each in application code without needing to extract out needless modules or targets:

struct AnalyticsClient {
  var track: (Event) -> Void

  struct Event: Equatable {
    var name: String
    var properties: [String: String]
  }
}

import XCTestDynamicOverlay

extension AnalyticsClient {
  static let testValue = Self(
    track: { _ in XCTFail("\(Self.self).track is unimplemented.") }
  )
}

XCTest Dynamic Overlay also comes with a helper that simplifies this exact pattern: unimplemented. It creates failing closures for you:

extension AnalyticsClient {
  static let testValue = Self(
    track: unimplemented("\(Self.self).track")
  )
}

And it can simplify the work of more complex dependency endpoints, which can throw or need to return a value:

struct AppDependencies {
  var date: () -> Date = Date.init,
  var fetchUser: (User.ID) async throws -> User,
  var uuid: () -> UUID = UUID.init
}

extension AppDependencies {
  static let testValue = Self(
    date: unimplemented("\(Self.self).date", placeholder: Date()),
    fetchUser: unimplemented("\(Self.self).fetchUser"),
    uuid: unimplemented("\(Self.self).uuid", placeholder: UUID())
  )
}

The above placeholder parameters can be left off, but will fatal error when the endpoint is called.

Documentation

Full documentation can be found here.

License

This library is released under the MIT license. See LICENSE for details.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].