7

I just upgraded to Xcode 6 beta 4, where the Swift compiler now supports access modifiers.

That caused a problem for me, since my unit tests now fail to compile (due to the classes not being public).

The simple solution is of course to make all tested classes public, but that feels like a hack (my personal preference is to write unit tests even on non-public classes).

In .NET and Java, you can normally allow unit tests assembly-level (or bundle-level in Java/OSGi) access to the assembly under test from the unit test assembly. I did not understand how to do something similar in Swift. Do I really have to make all my classes public to unit test them?

Krumelur
  • 31,081
  • 7
  • 77
  • 119
  • I don't really mind because usually I want to test only the public APIs. Testing every class and every method is a waste. – Sulthan Jul 28 '14 at 11:56
  • In the end it is a matter of philosoply and there are different opinions / strategies. I don't test everything, but I try to keep my public APIs as small as possible. I prefer IoC-like solutions, but in the kind of app this is it feels like complete overkill. – Krumelur Jul 28 '14 at 13:10

4 Answers4

10

This is a known issue and mentioned in the Beta 4 release notes. You might want to hold off changing your designs until more information is provided.

We're aware that our access control design isn't great for unit testing (and this was in the release notes), we're evaluating the situation to see what we can do.

-- Chris Lattner

A limitation of the access control system is that unit tests cannot interact with the classes and methods in an application unless they are marked public. This is because the unit test target is not part of the application module.

-- Xcode beta 4 release notes

https://github.com/ksm/SwiftInFlux#limitations-of-current-access-control-design

Community
  • 1
  • 1
Chris Wagner
  • 20,773
  • 8
  • 74
  • 95
  • 1
    Thanks for the info! I'm making everything public for now, and let's hope for some improvement in that area in the next Xcode release then. – Krumelur Jul 28 '14 at 20:56
  • Chris.. i cant find any update on this in the later BETAs.. has Apple just released a product that makes unit testing hard?! – Thomas Clowes Sep 10 '14 at 10:34
  • I haven't heard anything about it myself either. That's a shame if they didn't address this in 1.0. Maybe they will with the Xcode 6.1 release with Yosemite. – Chris Wagner Sep 10 '14 at 17:01
10

With Swift 2 you are now allowed to test your class without have to marked it as public. You just have to use the keyword @testable and the compiler will take care of rest.

Slide from What's new in Xcode WWDC 2015:

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Max_Power89
  • 1,710
  • 1
  • 21
  • 38
1

You can just add the source files from your target to the test target. Then they will be a part of your test target and you will be able to access them.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • 1
    Tried that, but then they belong to another module (the test module). Thus instances of the test-created classes cannot be cast to instances of the app-created classes. – Krumelur Jul 28 '14 at 12:01
  • @Krumelur Is that a problem if you do it with all your sources files? – Sulthan Jul 28 '14 at 12:22
  • 1
    Well, now that the classes are separate (as opposed to ObjC) I can foresee a lot of issues. The most immediate one for me is CoreData managed classes. – Krumelur Jul 28 '14 at 13:06
  • @Krumelur hmm, that could be a problem. – Sulthan Jul 28 '14 at 13:16
  • Well, I'm being pragmatic and making my "internal" API public :) – Krumelur Jul 28 '14 at 13:25
0

I think I have a better solution than making everything public. Simply make the StoryBoard a member of the test target just as you do with all your ViewControllers. Then create the StoryBoard in your test class using your test bundle instead of using nil or the main bundle. Check my post here for sample code.

var storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle(forClass: self.dynamicType))
vc = storyboard.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController
vc.loadView()
Mike Cole
  • 1,291
  • 11
  • 19