Testing internal classes in Obj-C module

I'm switching an old Objective-C project into the SPM project structure. Have questions about testing internal classes of a module.

Given an example of two targets:

// Package.swift
.target("MyTarget", cSettings: [ 
    .headerSearchPath("MyInternalStuff/MyInternalClass.h")
]),
.testTarget("MyTargetTests", dependencies: ["MyTarget"])

I have a test file inside the MyTargetTests where I can get access to the public classes of MyTarget using the @import MyTarget.

#import <XCTest/XCTest.h>

@import MyTarget;

@interface MyInternalClassTests : XCTestCase

@property (nonatomic, retain) MyPublicClass myPublicObject;      // works great

@property (nonatomic, retain) MyInternalClass myInternalObject;  // Unknown type name 'MyInternalClass'

@end

In Swift, this would be accomplished with @testable import MyTarget. I'm wondering if I should be using a different mechanism to import in my testTarget such that I have access to internal classes?

I've tried the following to no avail:

#import "MyInternalClass.h"

#import "MyTarget/MyInternalClass.h"

#import <MyTarget/MyInternalClass.h>

Wondering if anyone has had any luck testing module internals in an objective-c target and test target.

I wanted to check if/how this would work inside a test target implemented in Swift.

Short answer is it appears it doesn't.

I added a new testTarget written in swift to try this out:

// Package.swift
.target("MyTarget", cSettings: [ 
    .headerSearchPath("MyInternalStuff/MyInternalClass.h")
]),
.testTarget("MyTargetTests", dependencies: ["MyTarget"]),
.testTarget("MyTargetTestsSwift", dependencies: ["MyTarget"])

In this MyTargetTestsSwift I have a test class:


import XCTest

@testable import MyTarget

final class MyTargetInternalClass Tests: XCTestCase {

    func testExample() throws {
        let publicObj = MyTargetPublicClass()
        print(publicObj)

        let internalObj = MyTargetInternalClass()            // Cannot find 'MyTargetInternalClass' in scope. Quick fix "Import MyTarget"
        print(internalObj)
    }

}

What is really odd is Xcode will tell me to import the module that it already has imported. I can confirm because the MyTargetPublicClass is printed fine if I comment out the lower half of the test.


I feel like the only workaround would be to get really funky with the headerSearchPaths of my test target to include all the sources from the main target. This doesn't feel like a good way to do this though.

We achieve this by putting the private headers we want to expose in the public headers dir, and then use a custom modulemap to divide the headers into modules. This results in import Realm importing only the headers which are part of the umbrella header, and import Realm.Private imports the private headers as well. This works when importing from both obj-c and Swift.

Thanks for the quick response. I was able to accomplish what I needed using a custom modulemap and an inner module.

I didn't need to put the private headers into the include directory of the module, but my header definitions are relative paths that use .. to escape. Not sure if this is a good way to do it. I like being able to keep my public/private headers separate though.

module MyTarget {
    export *
    umbrella "."

    explicit module Internal {
        header "../MyInternalStuff/MyInternalClass.h"
    }
}

This allows me to do the following to import internal classes in the testTarget:

@import MyTarget.Internal;