36

I'm writing a BDD unit test for a public method. The method changes a private property (private var) so I'd like to write an expect() and ensure it's being set correctly. Since it's private, I can't work out how access it from the unit test target.

For Objective-C, I'd just add an extension header. Are there any similar tricks in Swift? As a note, the property has a didSet() with some code as well.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Robert Gummesson
  • 5,630
  • 2
  • 17
  • 16

1 Answers1

47

(Note that Swift 2 adds the @testable attribute which can make internal methods and properties available for testing. See @JeremyP's comments below for some more information.)

No. In Swift, private is private. The compiler can use this fact to optimize, so depending on how you use that property, it is legal for the compiler to have removed it, inlined it, or done any other thing that would give the correct behavior based on the code actually in that file. (Whether the optimizer is actually that smart today or not, it's allowed to be.)

Now of course if you declare your class to be @objc, then you can break those optimizations, and you can go poking around with ObjC to read it. And there are bizarre workarounds that can let you use Swift to call arbitrary @objc exposed methods (like a zero-timeout NSTimer). But don't do that.

This is a classic testing problem, and the classic testing answer is don't test this way. Don't test internal state. If it is literally impossible to tell from the outside that something has happened, then there is nothing to test. Redesign the object so that it is testable across its public interface. And usually that means composition and mocks.

Probably the most common version of this problem is caching. It's very hard to test that something is actually cached, since the only difference may be that it is retrieved faster. But it's still testable. Move the caching functionality into another object, and let your object-under-test accept a custom caching object. Then you can pass a mock that records whether the right cache calls were made (or networking calls, or database calls, or whatever the internal state holds).

Basically the answer is: redesign so that it's easier to test.

OK, but you really, really, really need it... how to do it? OK, it is possible without breaking the world.

Create a function inside the file to be tested that exposes the thing you want. Not a method. Just a free function. Then you can put that helper function in an #if TEST, and set TEST in your testing configuration. Ideally I'd make the function actually test the thing you care about rather than exposing the variable (and in that case, maybe you can let the function be internal or even public). But either way.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Thanks for the answer. I've been holding off accepting this as an answer as I thought there would be a few more options but they all failed so I am back to square one. I hope Apple comes up with a better way to handle this in a future version of Swift. I am not a big fan of wrapping my private class member into another object just for the sake of making it testable. In some cases, I would end up with objects that only holds one property. But I understand where you are coming from. I have a feeling that many developers will make properties public just to lazily work around the problem. – Robert Gummesson May 27 '15 at 18:27
  • 4
    This is no longer the case in swift 2.0 btw @testable attribute for unit testing makes internal methods public when built for testing – Jeef Jun 15 '15 at 14:58
  • 9
    This is a really good answer except for the first sentence which is not true. `@testable` does not make private variables available for testing, only internal ones. Thus Swift 2 does not make the problem trivial, and with good reason IMO. If there's no way to see the effect of internal state from the outside, who cares what it is? – JeremyP Jun 15 '15 at 15:37
  • 12
    Not trying to start a flame war, just to offer a different opinion - I actually kind of disagree with this. IMO it's an over-generalization to make an absolute statement like saying it's never useful to test private state or a private method. – danny Mar 10 '16 at 21:28
  • (submitted too soon) One pattern I've used (and I'm sure lots of people will hate this, but it's an option) is not to use actual private methods, and instead preface variables with `_` when they're meant to be private (like you do in python which has no concept of private). Then you can test or not test them as it makes sense, and there's still an indication it's private. – danny Mar 10 '16 at 21:34