jeudi 15 octobre 2020

How to expose internal methods of a Swift package written in Objective-C for the tests written in Swift?

I am porting legacy Objective-C library to the Swift Package, which has Unit Tests written in Swift. Previously, this library was written using standard approach: Framework target / Xcode project, so the test target could import internal headers (using bridging header). It seems as Swift Package manager does not offer anything like this, therefore testing internals looks problematic (without exposing them to the public). I tried to cheat it with exposing them using:

cSettings: [.headerSearchPath("../ObjcPackage/Source/Internal")]

But it does not work.

I am using swift-tools-version:5.3

Repo demonstrating the problem:

https://github.com/lukaszmargielewski/ObjcPackage

Some snippets:

1. Package definition:

// swift-tools-version:5.3
// Package.swift

import PackageDescription

let package = Package(
    name: "ObjcPackage",
    platforms: [
        .iOS(.v10)
    ],
    products: [
        .library(name: "ObjcPackage", targets: ["ObjcPackage"]),
    ],
    targets: [
        .target(
            name: "ObjcPackage",
            path: "ObjcPackage/Source",
            publicHeadersPath: "Public",
            cSettings: [
                .headerSearchPath("Public"),
                .headerSearchPath("Internal"),
            ]
        ),
        .testTarget(
            name: "ObjcPackageTests",
            dependencies: ["ObjcPackage"],
            path: "ObjcPackageTests",
            sources: ["Source"],
            cSettings: [
                .headerSearchPath("../ObjcPackage/Source/Internal")
            ]
        )
    ]
)

2. Public header - ObjcPackage

// ObjcPackage.h
#import <Foundation/Foundation.h>

@interface ObjcPackage : NSObject

- (nullable instancetype)initWithTitle:(nonnull NSString *)title NS_DESIGNATED_INITIALIZER;

- (nonnull instancetype)init NS_UNAVAILABLE;

@property (nonnull, readonly, nonatomic, copy) NSString *title;

@end

3. Internal header - ObjcPackage

// ObjcPackage+Internal.h
#import <Foundation/Foundation.h>
#import "ObjcPackage.h"

/// Internal methods of ObjcPackage
@interface ObjcPackage ()

- (NSString *)generateInternalSecret;

@end

4. Implementation file - ObjcPackage

// ObjcPackage.m
#import "ObjcPackage.h"
#import "ObjcPackage+Internal.h"

@interface ObjcPackage()

@property (nonatomic, readwrite, copy) NSString *title;

@end

@implementation ObjcPackage

- (instancetype)initWithTitle:(NSString *)title {
    self = [super init];
    if (self != nil) {
        self.title = title;
    }
    return self;
}

- (NSString *)generateInternalSecret {
    return [NSString stringWithFormat:@"%@_internal_secret", self.title];
}

@end

5. Test example (ObjcPackageTests)

// ObjcPackageTests.swift
import Foundation
import XCTest
@testable import ObjcPackage

class ObjcPackageTests: XCTestCase {

    private lazy var objcPackage = ObjcPackage(title: "A")

    func testPublic() {
        XCTAssertEqual(objcPackage?.title, "A")
    }

    func testInternal() {
        // How to expose internal Objective-C header to the test package, 
        // without making it public?
        // !!! - COMPILATION ERROR: - !!!
        XCTAssertEqual(objcPackage.generateInternalSecret, "A_internal_secret")
    }
}

Aucun commentaire:

Enregistrer un commentaire