Files
SwiftFormat/Tests/Rules/PreferSwiftTestingTests.swift

917 lines
29 KiB
Swift

//
// PreferSwiftTestingTests.swift
// SwiftFormatTests
//
// Created by Cal Stephens on 1/25/25.
// Copyright © 2025 Nick Lockwood. All rights reserved.
//
import XCTest
@testable import SwiftFormat
final class PreferSwiftTestingTests: XCTestCase {
func testConvertsSimpleTestSuite() {
let input = """
@testable import MyFeatureLib
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyFeatureWorks() {
let myFeature = MyFeature()
myFeature.runAction()
XCTAssertTrue(myFeature.worksProperly)
XCTAssertEqual(myFeature.screens.count, 8)
}
func testMyFeatureHasNoBugs() {
let myFeature = MyFeature()
myFeature.runAction()
XCTAssertFalse(myFeature.hasBugs, "My feature has no bugs")
XCTAssertEqual(myFeature.crashes.count, 0, "My feature doesn't crash")
XCTAssertNil(myFeature.crashReport)
}
}
"""
let output = """
import Foundation
@testable import MyFeatureLib
import Testing
final class MyFeatureTests {
@Test func `my feature works`() {
let myFeature = MyFeature()
myFeature.runAction()
#expect(myFeature.worksProperly)
#expect(myFeature.screens.count == 8)
}
@Test func `my feature has no bugs`() {
let myFeature = MyFeature()
myFeature.runAction()
#expect(!myFeature.hasBugs, "My feature has no bugs")
#expect(myFeature.crashes.isEmpty, "My feature doesn't crash")
#expect(myFeature.crashReport == nil)
}
}
"""
let options = FormatOptions(swiftVersion: "6.2")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .sortImports, .isEmpty, .swiftTestingTestCaseNames], options: options)
}
func testConvertsTestSuiteWithSetUpTearDown() {
let input = """
@testable import MyFeatureLib
import XCTest
final class MyFeatureTests: XCTestCase {
private var myFeature: MyFeature!
override func setUp() async throws {
try await super.setUp()
myFeature = try await MyFeature()
}
override func tearDown() {
super.tearDown()
myFeature = nil
}
func testMyFeatureHasNoBugs() {
myFeature.runAction()
XCTAssertFalse(myFeature.hasBugs, "My feature has no bugs")
XCTAssertEqual(myFeature.crashes.count, 0, "My feature doesn't crash")
XCTAssertNil(myFeature.crashReport)
}
}
"""
let output = """
import Foundation
@testable import MyFeatureLib
import Testing
final class MyFeatureTests {
private var myFeature: MyFeature!
init() async throws {
myFeature = try await MyFeature()
}
deinit {
myFeature = nil
}
@Test func myFeatureHasNoBugs() {
myFeature.runAction()
#expect(!myFeature.hasBugs, "My feature has no bugs")
#expect(myFeature.crashes.isEmpty, "My feature doesn't crash")
#expect(myFeature.crashReport == nil)
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .sortImports, .isEmpty], options: options)
}
func testConvertsSimpleXCTestHelpers() {
let input = """
import XCTest
class HelperConversionTests: XCTestCase {
func testConvertsSimpleXCTestHelpers() throws {
XCTAssert(foo)
XCTAssert(foo, "foo is true")
XCTAssert(foo, "foo" + " is true")
XCTAssertTrue(foo)
XCTAssertTrue(foo, "foo is true")
XCTAssertFalse(foo)
XCTAssertFalse(foo, "foo is false")
XCTAssertFalse(foo() == bar(), "foo is not equal to bar")
XCTAssertFalse(Foo(hasBar: foo == bar).isValid)
XCTAssertFalse(try foo)
XCTAssertFalse(try! foo.bar.baz())
XCTAssertFalse(foo is Bar)
XCTAssertNil(foo)
XCTAssertNil(foo, "foo is nil")
XCTAssertNotNil(foo)
XCTAssertNotNil(foo, "foo is not nil")
XCTAssertEqual(foo, bar)
XCTAssertEqual(foo, bar, "foo and bar are equal")
XCTAssertEqual(foo, bar, "foo and bar" + " are equal")
XCTAssertEqual(foo == bar, foo == baaz)
XCTAssertEqual(foo is Bar, false)
XCTAssertNotEqual(foo == bar, foo == baaz)
XCTAssertNotEqual(foo, bar)
XCTAssertNotEqual(foo, bar, "foo and bar are different")
XCTAssertIdentical(foo, bar)
XCTAssertIdentical(foo, bar, "foo and bar are the same reference")
XCTAssertNotIdentical(foo, bar)
XCTAssertNotIdentical(foo, bar, "foo and bar are different references")
XCTAssertGreaterThan(foo, bar)
XCTAssertGreaterThan(foo, bar, "foo is greater than bar")
XCTAssertGreaterThanOrEqual(foo, bar)
XCTAssertGreaterThanOrEqual(foo, bar, "foo is greater than or equal bar")
XCTAssertLessThan(foo, bar)
XCTAssertLessThan(foo, bar, "foo is less than bar")
XCTAssertLessThanOrEqual(foo, bar)
XCTAssertLessThanOrEqual(foo, bar, "foo is less than or equal bar")
XCTFail()
XCTFail("Unexpected issue")
XCTFail("Unexpected" + " " + "issue")
XCTFail(someStringValue)
try XCTUnwrap(foo)
try XCTUnwrap(foo, "foo should not be nil")
XCTAssertThrowsError(try foo.bar)
XCTAssertThrowsError(try foo.bar, "foo.bar should throw an error")
XCTAssertNoThrow(try foo.bar)
XCTAssertNoThrow(try foo.bar, "foo.bar should not throw an error")
}
}
"""
let output = """
import Foundation
import Testing
class HelperConversionTests {
@Test func convertsSimpleXCTestHelpers() throws {
#expect(foo)
#expect(foo, "foo is true")
#expect(foo, Comment(rawValue: "foo" + " is true"))
#expect(foo)
#expect(foo, "foo is true")
#expect(!foo)
#expect(!foo, "foo is false")
#expect(!(foo() == bar()), "foo is not equal to bar")
#expect(!Foo(hasBar: foo == bar).isValid)
#expect(try !foo)
#expect(!(try! foo.bar.baz()))
#expect(!(foo is Bar))
#expect(foo == nil)
#expect(foo == nil, "foo is nil")
#expect(foo != nil)
#expect(foo != nil, "foo is not nil")
#expect(foo == bar)
#expect(foo == bar, "foo and bar are equal")
#expect(foo == bar, Comment(rawValue: "foo and bar" + " are equal"))
#expect((foo == bar) == (foo == baaz))
#expect((foo is Bar) == false)
#expect((foo == bar) != (foo == baaz))
#expect(foo != bar)
#expect(foo != bar, "foo and bar are different")
#expect(foo === bar)
#expect(foo === bar, "foo and bar are the same reference")
#expect(foo !== bar)
#expect(foo !== bar, "foo and bar are different references")
#expect(foo > bar)
#expect(foo > bar, "foo is greater than bar")
#expect(foo >= bar)
#expect(foo >= bar, "foo is greater than or equal bar")
#expect(foo < bar)
#expect(foo < bar, "foo is less than bar")
#expect(foo <= bar)
#expect(foo <= bar, "foo is less than or equal bar")
Issue.record()
Issue.record("Unexpected issue")
Issue.record(Comment(rawValue: "Unexpected" + " " + "issue"))
Issue.record(Comment(rawValue: someStringValue))
try #require(foo)
try #require(foo, "foo should not be nil")
#expect(throws: Error.self) { try foo.bar }
#expect(throws: Error.self, "foo.bar should throw an error") { try foo.bar }
#expect(throws: Never.self) { try foo.bar }
#expect(throws: Never.self, "foo.bar should not throw an error") { try foo.bar }
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .wrapArguments, .indent, .redundantParens, .hoistTry], options: options, exclude: [.noForceTryInTests])
}
func testConvertsMultilineXCTestHelpers() {
let input = """
import XCTest
class HelperConversionTests: XCTestCase {
func test_converts_multiline_XCTest_helpers() {
XCTAssert(foo.bar(
baaz: "baaz",
quux: "quux"))
XCTAssertEqual(
// Comment before first argument
foo.bar.baaz("quux"),
// Comment before second argument
Foo(bar: "bar", baaz: "Baaz"))
XCTAssert(
// Comment before first argument
foo.bar(baaz: "baaz", quux: "quux"),
// Comment before message
"foo is valid")
XCTAssertEqual(
// Comment before first argument
foo.bar.baaz("quux"),
// Comment before second argument
Foo(bar: "bar", baaz: "Baaz"),
// Comment before message
"foo matches expected value")
XCTFail(
// Comment before multiline string
#\"\"\"
Multiline string
in method call
\"\"\"#)
XCTAssertFalse(
// Comment before first argument
foo.bar.baaz.quux,
// Comment before second argument
"foo.bar.baaz.quux is false")
}
}
"""
let output = """
import Foundation
import Testing
class HelperConversionTests {
@Test func converts_multiline_XCTest_helpers() {
#expect(foo.bar(
baaz: "baaz",
quux: "quux"))
#expect(
// Comment before first argument
foo.bar.baaz("quux") ==
// Comment before second argument
Foo(bar: "bar", baaz: "Baaz"))
#expect(
// Comment before first argument
foo.bar(baaz: "baaz", quux: "quux"),
// Comment before message
"foo is valid")
#expect(
// Comment before first argument
foo.bar.baaz("quux") ==
// Comment before second argument
Foo(bar: "bar", baaz: "Baaz"),
// Comment before message
"foo matches expected value")
Issue.record(
// Comment before multiline string
#\"\"\"
Multiline string
in method call
\"\"\"#)
#expect(
// Comment before first argument
!foo.bar.baaz.quux,
// Comment before second argument
"foo.bar.baaz.quux is false")
}
}
"""
let options = FormatOptions(closingParenPosition: .sameLine, swiftVersion: "6.0")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .wrapArguments, .indent, .trailingSpace], options: options)
}
func testPreservesUnsupportedExpectationHelpers() {
let input = """
@testable import MyFeatureLib
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyAsyncFeatureWorks() {
let expectation = expectation(description: "my feature runs async")
MyFeature().run {
expectation.fulfill()
}
wait(for: [expectation])
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, rule: .preferSwiftTesting, options: options)
}
func testPreservesUnsupportedUITestHelpers() {
let input = """
@testable import MyFeatureLib
import XCTest
final class MyFeatureTests: XCTestCase {
func testUITest() {
let app = XCUIApplication()
app.buttons["Learn More"].tap()
XCTAssert(app.staticTexts["Success"].exists)
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, rule: .preferSwiftTesting, options: options)
}
func testPreservesUnsupportedPerformanceTestHelpers() {
let input = """
@testable import MyFeatureLib
import XCTest
final class MyFeatureTests: XCTestCase {
func testPerformance() {
measure {
MyFeature.expensiveOperation()
}
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, rule: .preferSwiftTesting, options: options)
}
func testPreservesAsyncOrThrowsTearDown() {
let input = """
@testable import MyFeatureLib
import XCTest
final class MyFeatureTests: XCTestCase {
private var myFeature: MyFeature!
override func setUp() async throws {
try await super.setUp()
myFeature = try await MyFeature()
}
/// deinit can't be async / throws
override func tearDown() async throws {
super.tearDown()
try await myFeature.cleanUp()
}
func testMyFeatureHasNoBugs() {
myFeature.runAction()
XCTAssertFalse(myFeature.hasBugs)
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, rule: .preferSwiftTesting, options: options)
}
func testPreservesUnsupportedMethodOverride() {
let input = """
@testable import MyFeatureLib
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyFeatureWorks() {
let myFeature = MyFeature()
myFeature.runAction()
XCTAssertTrue(myFeature.worksProperly)
}
override func someUnknownOveride() {
super.someUnknownOveride()
print("test")
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, rule: .preferSwiftTesting, options: options)
}
func testConvertsHelpersInHelperMethods() {
let input = """
@testable import MyFeatureLib
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyFeatureWorks() {
let myFeature = MyFeature()
assertMyFeatureWorks(myFeature)
}
}
func assertMyFeatureWorks(_ feature: MyFeature) {
XCTAssert(feature.works)
}
"""
let output = """
import Foundation
@testable import MyFeatureLib
import Testing
final class MyFeatureTests {
@Test func myFeatureWorks() {
let myFeature = MyFeature()
assertMyFeatureWorks(myFeature)
}
}
func assertMyFeatureWorks(_ feature: MyFeature) {
#expect(feature.works)
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .sortImports], options: options)
}
func testPreservesHelpersWithLineFileParams() {
let input = """
@testable import MyFeatureLib
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyFeatureWorks() {
let myFeature = MyFeature()
assertMyFeatureWorks(myFeature)
}
}
func assertMyFeatureWorks(_ feature: MyFeature, file: StaticString = #file, line: UInt = #line) {
XCTAssert(feature.works, file: file, line: line)
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, rule: .preferSwiftTesting, options: options)
}
func testDoesntUpdateNameToIdentifierRequiringBackTicks() {
let input = """
import XCTest
final class MyFeatureTests: XCTestCase {
func test123() {
XCTAssertEqual(1 + 2, 3)
}
func testInit() {
XCTAssertNotNil(Foo())
}
func testSubscript() {
XCTAssertNotNil(foo[bar])
}
func testNil() {
XCTAssertNil(foo.optionalFoo)
}
}
"""
let output = """
import Foundation
import Testing
final class MyFeatureTests {
@Test func test123() {
#expect((1 + 2) == 3)
}
@Test func testInit() {
#expect(Foo() != nil)
}
@Test func testSubscript() {
#expect(foo[bar] != nil)
}
@Test func testNil() {
#expect(foo.optionalFoo == nil)
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .sortImports], options: options)
}
func testDoesntUpdateTestNameToExistingFunctionName() {
let input = """
import XCTest
final class MyFeatureTests: XCTestCase {
func testOnePlusTwo() {
XCTAssertEqual(onePlusTwo(), 3)
}
private func onePlusTwo() -> Int {
1 + 2
}
}
"""
let output = """
import Foundation
import Testing
final class MyFeatureTests {
@Test func testOnePlusTwo() {
#expect(onePlusTwo() == 3)
}
private func onePlusTwo() -> Int {
1 + 2
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .sortImports], options: options)
}
func testPreservesTestMethodWithArguments() {
let input = """
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyFeatureWorks() {
testMyFeatureWorks(MyFeature())
}
private func testMyFeatureWorks(_ feature: Feature) {
feature.runAction()
XCTAssertTrue(feature.worksProperly)
}
}
"""
let output = """
import Foundation
import Testing
final class MyFeatureTests {
@Test func myFeatureWorks() {
testMyFeatureWorks(MyFeature())
}
private func testMyFeatureWorks(_ feature: Feature) {
feature.runAction()
#expect(feature.worksProperly)
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .sortImports], options: options)
}
func testAddsThrowingEffectIfNeeded() {
// XCTest helpers all have throwing autoclosure params,
// so can have `try` without the test case being `throws`.
// #exect doesn't work like this, so the test case has to be throwing.
let input = """
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyFeatureWorks() {
let myFeature = MyFeature()
XCTAssertTrue(try feature.worksProperly)
}
func testMyFeatureWorksAsync() async {
let myFeature = await MyFeature()
XCTAssertTrue(try feature.worksProperly)
}
}
"""
let output = """
import Foundation
import Testing
final class MyFeatureTests {
@Test func myFeatureWorks() throws {
let myFeature = MyFeature()
#expect(try feature.worksProperly)
}
@Test func myFeatureWorksAsync() async throws {
let myFeature = await MyFeature()
#expect(try feature.worksProperly)
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .sortImports], options: options)
}
func testPreservesAdditionalXCTestSymbols() {
let input = """
@testable import MyFeatureLib
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyFeatureWorks() {
let myFeature = MyFeature()
myCustomTestHelper(myFeature) // XCTest-based helper defined in another file
}
}
"""
let options = FormatOptions(additionalXCTestSymbols: ["myCustomTestHelper"], swiftVersion: "6.0")
testFormatting(for: input, rule: .preferSwiftTesting, options: options)
}
func testPreservesXCTestCaseExtension() {
let input = """
@testable import MyFeatureLib
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyFeatureWorks() {
let myFeature = MyFeature()
myHelper()
XCTAssert(myFeature.works)
}
}
extension XCTestCase {
func myHelper() {
print("...")
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, rule: .preferSwiftTesting, options: options)
}
func testAddsUIKitImportIfNeeded() {
let input = """
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyFeatureWorks() {
let viewController = UIViewController()
XCTAssertNotNil(viewController.view)
}
}
"""
let output = """
import Foundation
import Testing
import UIKit
final class MyFeatureTests {
@Test func myFeatureWorks() {
let viewController = UIViewController()
#expect(viewController.view != nil)
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .sortImports], options: options)
}
func testAppliesCustomTestSuiteAttribute() {
let input = """
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyFeatureWorks() {
let myFeature = MyFeature()
XCTAssertTrue(myFeature.worksProperly)
}
}
"""
let output = """
import Foundation
import Testing
@MainActor
final class MyFeatureTests {
@Test func myFeatureWorks() {
let myFeature = MyFeature()
#expect(myFeature.worksProperly)
}
}
"""
let options = FormatOptions(defaultTestSuiteAttributes: ["@MainActor"], swiftVersion: "6.0")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .sortImports], options: options)
}
func testAppliesMultipleTestSuiteAttributes() {
let input = """
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyFeatureWorks() {
let myFeature = MyFeature()
XCTAssertTrue(myFeature.worksProperly)
}
}
"""
let output = """
import Foundation
import Testing
@MainActor @Suite(.serialized)
final class MyFeatureTests {
@Test func myFeatureWorks() {
let myFeature = MyFeature()
#expect(myFeature.worksProperly)
}
}
"""
let options = FormatOptions(defaultTestSuiteAttributes: ["@MainActor", "@Suite(.serialized)"], swiftVersion: "6.0")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .sortImports], options: options)
}
func testConvertsTestCaseExtensionInSameFile() {
let input = """
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyFeatureWorks() {
let myFeature = MyFeature()
XCTAssertTrue(myFeature.worksProperly)
}
}
extension MyFeatureTests {
func testAnotherFeatureWorks() {
let anotherFeature = AnotherFeature()
XCTAssertFalse(anotherFeature.hasBugs)
}
}
"""
let output = """
import Foundation
import Testing
final class MyFeatureTests {
@Test func myFeatureWorks() {
let myFeature = MyFeature()
#expect(myFeature.worksProperly)
}
}
extension MyFeatureTests {
@Test func anotherFeatureWorks() {
let anotherFeature = AnotherFeature()
#expect(!anotherFeature.hasBugs)
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .sortImports], options: options)
}
func testConvertsTestCaseExtensionWithSetUpTearDown() {
let input = """
import XCTest
final class MyFeatureTests: XCTestCase {
private var myFeature: MyFeature!
override func setUp() {
myFeature = MyFeature()
}
override func tearDown() {
myFeature = nil
}
func testMyFeatureWorks() {
XCTAssertTrue(myFeature.worksProperly)
}
}
extension MyFeatureTests {
func testAnotherFeatureWorks() {
let anotherFeature = AnotherFeature()
XCTAssertFalse(anotherFeature.hasBugs)
}
}
"""
let output = """
import Foundation
import Testing
final class MyFeatureTests {
private var myFeature: MyFeature!
init() {
myFeature = MyFeature()
}
deinit {
myFeature = nil
}
@Test func myFeatureWorks() {
#expect(myFeature.worksProperly)
}
}
extension MyFeatureTests {
@Test func anotherFeatureWorks() {
let anotherFeature = AnotherFeature()
#expect(!anotherFeature.hasBugs)
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, [output], rules: [.preferSwiftTesting, .sortImports], options: options)
}
func testPreservesTestCaseExtensionWithUnsupportedFeature() {
let input = """
import XCTest
final class MyFeatureTests: XCTestCase {
func testMyFeatureWorks() {
let myFeature = MyFeature()
XCTAssertTrue(myFeature.worksProperly)
}
}
extension MyFeatureTests {
func testWithExpectation() {
let expectation = expectation(description: "test")
MyFeature().doAsync {
expectation.fulfill()
}
wait(for: [expectation])
}
}
"""
let options = FormatOptions(swiftVersion: "6.0")
testFormatting(for: input, rule: .preferSwiftTesting, options: options)
}
}