Skip to content

ruwatana/swifixture

Repository files navigation

πŸ§ͺ Swifixture

build Swift Version License

Swifixture is a tool built as a Swift Package that automatically generates fixture methods for Swift. This tool is inspired by uber/mockolo.

πŸ“š Table of Contents

πŸ“¦ Installation

πŸ’‘ Basic

git clone https://github.com/ruwatana/swifixture.git
cd swifixture
swift build

πŸ”§ Install in your Xcode Project

We also recommend to run Swifixture on your Xcode build phases by BuildTools package. (The approach using BuildTools is inspired by nicklockwood/SwiftFormat)

To set up Swifixture as an Xcode build phase, do the following:

  1. Create a BuildTools folder (if not already exists)

BuildTools folder is a folder that contains the Swifixture executable.

Because it is a build tool, it is not included in the main package.

mkdir -p BuildTools
  1. Create a Swift Package
cd BuildTools
swift package init
  1. Add Swifixture as a dependency in Package.swift
// Package.swift
import PackageDescription

let package = Package(
    name: "BuildTools",
    platforms: [
        .macOS(.v13)
    ],
    products: [
        .library(name: "BuildTools", targets: ["BuildTools"])
    ],
    dependencies: [
        .package(
            url: "https://github.com/ruwatana/swifixture.git",
            exact: Version("x.x.x")
        )
    ],
    targets: [
        .target(
            name: "BuildTools",
            dependencies: [
                .product(name: "swifixture", package: "Swifixture")
            ]
        )
    ]
)
  1. Add a build phase to your target

Open your project in Xcode, select your target, and go to the "Build Phases" tab. Click the "+" button and add a "Run Script" phase. In the script field, add the following command:

if [ ! -e "${SRCROOT}/BuildTools/.build/release/swifixture" ]; then
    xcrun --sdk macosx swift build -c release --package-path "${SRCROOT}/BuildTools"
fi

"${SRCROOT}/BuildTools/.build/release/swifixture"

[NOTE] In Xcode 15 and later, the User Script Sandboxing setting must be set to NO for this script to work properly.

image

πŸš€ Usage

Swifixture can be executed from the command line as an executable target. The basic syntax is as follows:

swift run swifixture [options]

πŸ”§ Options

Option Shorthand Description
--help -h Show help information.
--source <path> -s Specify the source file or directory path to search recursively for Swift files. Default is ./.
--output <path> -o Specify the output file for the generated fixture methods. Default is ./Generated/Fixtures.swift.
--additional-imports <module1> <module2> ... Specify additional module names to import. By default, Foundation is imported.
--testable-import <module> Specify a module name to testable import. By default, it is not imported.

πŸ’‘ Example

To generate fixture methods for a Swift file located at ./MyStruct.swift and output the results to ./Generated/Fixtures.swift by default.

you can run:

swift run swifixture --source ./MyStruct.swift

Using fixturable

You can annotate your struct with /// @fixturable to enable automatic generation of fixture methods.

For example:

// MyStruct.swift

/// @fixturable
struct User {
    let name: String
    let age: Int
}

This will generate fixture methods for the User struct on ./Generated/Fixtures.swift, allowing you to easily create test instances.

// Generated/Fixtures.swift

///
///  @Generated by Swifixture
///

import Foundation

extension User {
    static func fixture(
        name: String = "name",
        age: Int = 0
    ) -> Self {
        .init(
            name: name,
            age: age
        )
    }
}

Override Settings for Custom Initial Values

You can also specify custom initial values for properties using the /// @fixturable(override: key = value) annotation. For instance, if you have a custom enum and want to set a specific value, you can do it like this:

// MyStruct.swift

enum UserRole {
    case admin
    case user
}

/// @fixturable(override: role = .admin)
struct User {
    let name: String
    let age: Int
    let role: UserRole
}

In this example, the role property will default to .admin when generating fixture methods, allowing for more control over the test data.

Example of Generated Output

When you run the Swifixture tool with the above User struct, it will generate a file similar to the following:

// Generated/Fixtures.swift

///
///  @Generated by Swifixture
///

import Foundation

extension User {
    static func fixture(
        name: String = "name",
        age: Int = 0,
        role: UserRole = .admin
    ) -> Self {
        .init(
            name: name,
            age: age,
            role: role
        )
    }
}

Additional Imports and Testable Import

If you want to include additional imports and a testable import, you can do so like this:

swift run swifixture \
    --source ./MyStruct.swift \
    --output ./Generated/Fixtures.swift \
    --additional-imports Combine SwiftUI \
    --testable-import MyModule

then:

// Generated/Fixtures.swift

///
///  @Generated by Swifixture
///

import Foundation
import Combine
import SwiftUI

@testable import MyModule

...

🚧 Work In Progress

Currently, Swifixture only supports auto-generating fixture methods for struct.

Additionally, it is limited to our defined primitive types such as String, Int, Double, etc.

If you want to use custom types, consider using override settings or contribute to Swifixture!

🀝 Contributing

We welcome contributions to improve Swifixture! Please feel free to submit a pull request or open an issue for any bugs or feature requests.

Development

Open this project in Xcode and edit the code.

Test

We have prepared a xctestplan, so you can run the test in Xcode.

Build and Run

You can build and run Swifixture with swift command.

We have prepared a test source file in ./Tests/SwifixtureTests/Resources/Source.swift.

swift build
swift run swifixture --source ./Tests/SwifixtureTests/Resources/Source.swift

πŸ“„ License

This project is licensed under the Apache License, Version 2.0 - see the LICENSE file for details.