Contents

fenli/sourcerystencilpacks

> Convenient way to use collections of Sourcery stencils as an SPM plugins and Xcode plugins.

How to install

SPM

Add this configuration to your Package.swift:

dependencies: [
    .package(url: "https://github.com/fenli/SourceryStencilPacks", from: "0.3.2"),
],

Then add the plugins to your target:

.target(
    name: "MyPackage",
    plugins: [
        // Regular target plugins
        .plugin(name: "SourcePack", package: "SourceryStencilPacks")
    ]
)
.testTarget(
    name: "MyPackageTests",
    dependencies: ["MyPackage"],
    plugins: [
        // Test target plugins
        .plugin(name: "TestPack", package: "SourceryStencilPacks")
    ]
),

XCode

Integration into Xcode project:

  • In Xcode root project, navigate to your targets list in side bar.
  • Select target to integrate, eg: for TestPack plugins, select your *Tests target
  • Go to Build Phase -> Run Build Tool Plug-ins -> Add the plugin

Plugins

:rocket: SourcePack

Utility for generating boilerplate code like hashable, equatable, copyable, etc. Here are all the supported annotations: | Annotation | Description | |----------------------------|-------------| | Hashable | Implements Hashable into classes, structs or enums | | Equatable | Implements Equatable into classes, structs or enums | | Describable | Implements CustomStringConvertible into classes, structs or enums | | Copyable | Implements copy function into classes or structs | | DataRepresentable | Combine all Hashable, Equatable, Describable, and Copyable |

How to generate boilerplate functions
// Generate hash, equals(==) and copy functions
// sourcery: Hashable, Equatable, Copyable
struct Product {
    let name: String
    let price: Double
    let variants: [ProductVariant]
}

// Or simply use:
// sourcery: DataRepresentable
struct Product {
    let name: String
    let price: Double
    let variants: [ProductVariant]
}

:rocket: TestPack

Utility for generating test doubles like mocks, stubs, and fakes (by generating random object).

How to generate mocks and fakes

Use Mockable annotation to protocols to generate mocks/stubs and Randomizable to structs or enums to generate random fakes

// Generate ProductServiceMock() class
// sourcery: Mockable
class ProductService {
    let repository: ProductRepository

    init(repository: ProductRepository) {
        self.repository = productRepository
    }
    
    func getProducts() async throws -> [Product] {
        return try await repository.getAllProducts()
    }
}

// Generate ProductRepositoryMock() class
// sourcery: Mockable
protocol ProductRepository {
    
    func getAllProducts() async throws -> [Product]
}

// Generate Product.random() static function
// sourcery: Randomizable
struct Product {
    let name: String // String.random() automatically generated
    let price: Double // Double.random() automatically generated
    let variants: [ProductVariant] // Need to annotate also on ProductVariant
}

// Generate ProductVariant.random() and [ProductVariant].random()
// sourcery: Randomizable=+array
struct ProductVariant: Equatable {
    let id: Int
    let name: String
}
import Testing
@testable import SamplePackage

struct ProductServiceTests {
    
    private var productRepositoryMock: ProductRepositoryMock!
    private var service: ProductService!
    
    init() {
        productRepositoryMock = ProductRepositoryMock()
        service = ProductService(productRepository: productRepositoryMock)
    }

    @Test
    func testGetAllProductsSuccess() async throws {
        // Generate fakes with random object
        let fakeProducts = (0...5).map {_ in Product.random() }

        // Use generated mocks for mocking/stubbing
        productRepositoryMock.getAllProductsProductReturnValue = fakeProducts

        // Action
        let result = try await service.getProducts()
        
        // Asserts
        #expect(result == fakeProducts)
    }
}

[!NOTE] For primitive and standard types, random extension automatically generated. For example like String.random(), Int.random(), Double.random(), etc.

If plugins is applied in .testTarget, the Mocks and Random object only available from unit test. But, if you prefer to apply on regular .target please use config debug_only=true so it's not included in release binary.

For more sample usage, please see the SamplePackage.

Optional: Custom configuration

Default plugin configuration should be suitable on most cases, but in case you need to customize it you can create .testpack.json inside your package directory (same level as Package.swift). All configuration keys here should be present otherwise it will use default value for all config.

{
    "debug_only": true,
    "random_std_lib": true,
    "random_std_lib_protection": "fileprivate",
    "imports": [],
    "testable_imports": []
}

| Key | Default Value | Description | |----------------------------|----------------|-------------| | debug_only | false | Generate mocks/random for Debug build only. Default to false if applied on test target. If target is regular, then it's recommended to set as true | | random_std_lib | true | Generate std lib random extension | | random_std_lib_protection| internal | Protection level of std lib random extension. If target is regular, then it's recommended to set as fileprivate | | imports | ` | List of additional imports to generated sources | | testable_imports | ` | List of additional @testable imports to generated sources. Dependencies target is automatically added if target is test target. |

Other testing libs integration

If you are using MockSwift, this plugin also provide autocreating the mocks using annotation // sourcery: MockSwift in your protocol. See sample here.

Are you using other testing tools to generate mock manually? you can suggest this plugin to integrate with that tools by submitting new issue.

License

SourceryStencilPacks is available under the Apache License Version 2.0. See LICENSE for more information.

Package Metadata

Repository: fenli/sourcerystencilpacks

Default branch: main

README: README.md