Dirty Henry Dirty Henry - 1 month ago 8
iOS Question

Why does Travis not find my header file? (when the same instruction runs fine on my local Xcode)

My Cocoapod's lib project fails its tests on Travis because the test target seems to be unable to find one of the Pod's sources:

'XYZMyClass.h' file not found


On my development environment, I can run the same command Travis is running successfully and the environments are set the same:


  • The image is
    osx_image: xcode8
    for Travis and I use Xcode 8.0 locally as well

  • The instruction that fails on Travis but runs successfully locally is the following:

    set -o pipefail && xcodebuild -workspace Example/XYZMyPod.xcworkspace -scheme XYZMyPod-Example -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6s,OS=9.3' test | xcpretty



Even weirder, the 'XYZMyClass.h' is the 2nd import in my source file imports. Why does the 1st one works OK? They belong to the same target, with the same visibility (
Public
).

PS: the source is available on GitHub here and the Travis build is here.

Thank you!

Answer

The root of problem lies in your BSGMetrics.podspec

Upon pod install, these headers get public:

  s.public_header_files = [
    'BSGMetrics/Classes/BSGMetrics.h',
    'BSGMetrics/Classes/BSGMetricsConfiguration.h',
    'BSGMetrics/Classes/BSGMetricsEvent.h'
  ]

That is why in your test class you are able to see first import and not able to see second import.

#import "BSGMetricsEvent.h"
#import "BSGMetricsService.h"

If the service is intended to be private header, you won't be able to test it with this kind of installation - via Sample project with tests target and your pod installed as pod.

Instead you need to have your pod files added to project's main or test target (and I suggest test, not main).

Anyway, the simplest workaround here is to expose BSGMetricService into s.public_header_files

EDIT (Description on testing approaches):

The trick is how you treat unit tests. Think of BSGMetrics as of "module", a blackbox. It has some "visible" interface and some hidden implementation details. This is true for both Objective-C and Swift. The different is the language tools used to define visibility of your module entities. In Objective-C it's header files. In Swift it's public, private, internal modifiers (before Swift 3.0)

So, until you treat your BSGMetrics as a module while testing, you won't be able to access / test your implementation details. You will be able to test it only via available interface.

Is it bad or good? Not really, everything depends on what you need. However, if your expect on [BSGMetrics openWithConfiguration:] to have instance of BSGMetricsService initialized, you need BSGMetricsService to be visible to scope of your test. And there are two approaches: make is visible (the way you used for now) or merge module and test's scopes.

I'll describe a bit how I achieve the latter approach for testing one of my libraries.

First, test target is the part of same project, where all files under test are added as well.

Second, testing is done only inside test bundle, without host application. enter image description here

Third, files under test are added to compile in test target as well.

So long story short, tests and code under tests reside in same module.

More reading might be found here

Comments