Unit Testing of private methods in Xcode

Objective CCocoaXcodeTddOcunit

Objective C Problem Overview


I'm trying out test driven development in a toy project. I can get the tests working for the public interface to my classes (although I'm still on the fence because I'm writing more testing code than there is in the methods being tested).

I tend to use a lot of private methods becuase I like to keep the public interfaces clean; however, I'd still like to use tests on these methods.

Since Cocoa is a dynamic language, I can still call these private methods, but i get warnings in my tests that my class may not respond to these methods (although it clearly does). Since I like to compile with no warnings here are my questions:

  1. How do i turn off these warnings in Xcode?
  2. Is there something else I could do to turn off these warnings?
  3. Am I doing something wrong in trying 'white box' testing?

Objective C Solutions


Solution 1 - Objective C

Remember that there's actually no such thing as "private methods" in Objective-C, and it's not just because it's a dynamic language. By design, Objective-C has visibility modifiers for ivars, but not for methods — it's not by accident that you can call any method you like.

@Peter's suggestion is a great one. To complement his answer, an alternative I've used (when I don't want/need a header just for private methods) is to declare a category in the unit test file itself. (I use @interface MyClass (Test) as the name.) This is a great way to add methods that would be unnecessary bloat in the release code, such as for accessing ivars that the class under test has access to. (This is obviously less of an issue when properties are used.)

I've found this approach makes it easy to expose and verify internal state, as well as adding test-only methods. For example, in this unit test file, I wrote an -isValid method for verifying correctness of a binary heap. In production, this method would be a waste of space, since I assume a heap is valid — I only care about it when testing for unit test regressions if I modify the code.

Solution 2 - Objective C

> How do i turn off these warnings in Xcode?

Don't.

> Is there something else I could do to turn off these warnings?

Don't.

> Am I doing something wrong in trying 'white box' testing?

No.

The solution is to move your private methods to a category in its own header. Import this header into both the real class and test-case class implementation files.

Solution 3 - Objective C

Solution 4 - Objective C

I was dealing with the same issue when I started with TDD few days ago. I've found this very interesting point of view in Test-Driven iOS Development book:

> I have often been asked, “Should I test my private methods?” or the related question “How should I test my private methods?” People asking the second question have assumed that the answer to the first is “Yes” and are now looking for a way to expose their classes’ private interfaces in their test suites. > > My answer relies on observation of a subtle fact: You already have tested your private methods. By following the red–green–refactor approach common in test-driven development, you designed your objects’ public APIs to do the work those objects need to do. With that work specified by the tests—and the continued execution of the tests assuring you that you haven’t broken anything—you are free to organize the internal plumbing of your classes as you see fit. > >Your private methods are already tested because all you’re doing is refactoring behavior that you already have tests for. You should never end up in a situation where a private method is untested or incompletely tested, because you create them only when you see an opportunity to clean up the implementation of public methods. This ensures that the private methods exist only to support the class’s that they must be invoked during testing because they are definitely being called from public methods.

Solution 5 - Objective C

While having a private header or defining your own category are probably more correct solutions there is also another very simple solution: cast the object to (id) before calling the method.

Solution 6 - Objective C

If you don't want to distribute your private method implementations across multiple source files, a refinement to the Category solution is to define an Extension (essentially an anonymous Category - refer to Apple's documentation) in a header file that's imported by both your existing class' implementation and the relevant unit test source files.

Using an Extension allows the compiler to warn you if the implementation of the private method is not present in the main @implementation block. This link illustrates it nicely.

Solution 7 - Objective C

Easy Job. Steps:

  1. You have -(NSString*)getTestString; in your target m file for interface Foo

  2. Add a category in your unit test file:

    @interface DemoHomeViewController() -(NSString*)getTestString; @end

Then, do anything you want now.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAbizernView Question on Stackoverflow
Solution 1 - Objective CQuinn TaylorView Answer on Stackoverflow
Solution 2 - Objective CPeter HoseyView Answer on Stackoverflow
Solution 3 - Objective CAndrew BurnsView Answer on Stackoverflow
Solution 4 - Objective CAlois HolubView Answer on Stackoverflow
Solution 5 - Objective CJon SteinmetzView Answer on Stackoverflow
Solution 6 - Objective Cuser2067021View Answer on Stackoverflow
Solution 7 - Objective CHenry SouView Answer on Stackoverflow