Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test and private vars

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.

like image 425
Robert Gummesson Avatar asked Apr 20 '15 12:04

Robert Gummesson


People also ask

Can private functions be unit tested?

Unit Tests Should Only Test Public Methods The short answer is that you shouldn't test private methods directly, but only their effects on the public methods that call them. Unit tests are clients of the object under test, much like the other classes in the code that are dependent on the object.

How do you access a private variable in a test class?

Use the TestVisible annotation to allow test methods to access private or protected members of another class outside the test class. These members include methods, member variables, and inner classes. This annotation enables a more permissive access level for running tests only.

How do you write test cases for private methods in Swift?

You can't test private methods in Swift using @testable . You can only test methods marked either internal or public . As the docs say: Note: @testable provides access only for “internal” functions; “private” declarations are not visible outside of their file even when using @testable.


1 Answers

(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.

like image 163
Rob Napier Avatar answered Sep 21 '22 13:09

Rob Napier