4

I've seen this but it didn't really have a good answer. Sometimes you need to test some behavior of a class, but for you to really assert you had the expected behavior you need to inspect some of its private data.

An example: I'm creating a class which will return random words read from a file. So I designed a class like this:

public class WordsDatabase {
    private List<String> wordsList = new ArrayList<String>();
    
    public WordsDatabase() {
       fillWordsListFromFile();
    }

    private void fillWordsListFromFile() {...}

    public String getRandomWord() {...}
}

I don't want to expose the wordsList, but now how should I unit test if getRandomWord() is really getting me a random word from my dictionary text file?

All I can test is if it returns a word, but I don't know if the word is randomly being picked from the file.

In order to test that I could perform a Chi Square test for uniform distribution, but then I would have to know wordsList.size() at least, exposing it somehow.

Perhaps I'm just willing to perform too deep testing...

EDIT:

Thanks for the answers, got the tip. When my class is hard to test it may be because there is something wrong with its design.

Community
  • 1
  • 1
caiohamamura
  • 2,260
  • 21
  • 23
  • It is not exactly a duplicate, but this question is very close: http://stackoverflow.com/questions/34571/how-to-test-a-class-that-has-private-methods-fields-or-inner-classes?rq=1 – Denis Lukenich May 12 '16 at 15:40
  • I've seen it, but should I really mess with Reflections for that? – caiohamamura May 12 '16 at 15:42
  • 1
    this belongs in the http://programmers.stackexchange.com/ - a stackexchange site for programming theory/best practices. `Stack Overflow` (this site) is for specific programming/code questions. Your question is about Unit testing theory/best practices/approaches. – Don Cheadle May 12 '16 at 15:43
  • 1
    @mmcrae: It's not off-topic here. – Robert Harvey May 12 '16 at 15:44
  • I'll try that, stackexchange could have a "migrate to" feature for this... – caiohamamura May 12 '16 at 15:44
  • 2
    And a closely related question has been asked and answered there: http://programmers.stackexchange.com/q/199090/25768 – ratchet freak May 12 '16 at 15:45
  • 2
    Don't post on both sites. Pick *one.* – Robert Harvey May 12 '16 at 15:45
  • If you put the test class in the same package (not same source folder) as the class being tested, you can use package-private to allow test class access without exposing to the world. – Andreas May 12 '16 at 15:49
  • Thanks @ratchetfreak that discussion really helped out. – caiohamamura May 12 '16 at 15:50
  • 2
    a) things don't need to be fully `public`: http://stackoverflow.com/a/6913490/995891 b) your test could test a small test list of e.g. 10 things, call the regular method 1000 times and check that you got every thing roughly evenly. This currently doesn't work because your class fails to model it's dependencies properly and hardcodes them (aka make the list configurable) – zapl May 12 '16 at 15:50
  • Thanks @zapl that's is really the issue with my class. – caiohamamura May 12 '16 at 16:01

4 Answers4

2

You should test only public methods to be sure if your class works as you like.

Testing the internal state of an object is not a good practice, because the internal representation of the object can change, but the method behaviour can rest the same.

So you don't need to change visibility of a variable/method to unit test a class and you shouldn't test using Reflection (sometimes it is used as a tip to solve this kind of problems).


Note: if you need to know the size of wordsList. You need to check how wordsList is populated. From you code seems it is populated from a file. So you define the file to use in the test. Knowing the content of this file you don't need to check the internal value of wordsList size.

Davide Lorenzo MARINO
  • 26,420
  • 4
  • 39
  • 56
  • Thanks, I've though about using the same file in the testing code, but then I was afraid to be over complicating my unit test, besides I will have to expose a static TEXTFILEPATH to assure both are the same. – caiohamamura May 12 '16 at 15:53
2

"how should I unit test if getRandomWord() is really getting me a random word from my dictionary text file?"

This sounds like a perfect use case for mocks & dependency injection.

  1. Update your class so this dictionary is passed in at construction time
  2. In your unit tests, pass in different mock dictionaries that have very limited content / will return specific data when called
  3. After this update, your unit tests for getRandomWord are simply verifying that the expected data from your mock wordList was used.

If you're using a framework like spring, it's designed for dependency injection. This other answer has some good pointers on using springs to solve this.

Community
  • 1
  • 1
Krease
  • 15,805
  • 8
  • 54
  • 86
  • 1
    That is an interesting solution, so the class won't be tied to a single dictionary. Actually this is even better design, because how it was before it wasn't really a class, but almost an object. – caiohamamura May 12 '16 at 15:56
  • Would it not be a good idea to split the class into two, one for reading and another for getting a random word from a List? – LWood May 12 '16 at 16:12
  • @LukeW There's a lot of different ways to accomplish refactoring this to use dependency injection - that's one possible idea - another could be to extract a 'RandomNumberProvider' as another dependency. The "right" breakdown for this depends entirely on the surrounding context - ie: how does this particular class fit in? What else already exists that can be reused? What can be reused elsewhere? etc. – Krease May 12 '16 at 16:45
1

You should definitely not use reflection for JUnit testing unless you don't really have a choice. If you want to perform tests where private variables are involved you should use Injection or Autowiring.

Think about using these kinds of annotations:

  1. @Inject
  2. @Autowire
  3. @Bean
  4. @Produce
  5. @Value

In your case, for your list, @Inject or @Autowire is the most suited annotation to inject your variable. Remember you need to define an application context. Use Spring or just J2EE for that or any other great plaform that supports CDI.

On more thing, if you have private methods for a reason, then keep them that way. I think generally it is a bad idea to make methods public only to satisfy unit tests.

1

Changing your design would allow you to more easily test your class. You could, for example, inject a service/data access object to your WordsDatabase which actually retrieves words for you from some resource and has the benefit of being mocakable/swappable:

public class WordsDatabase {
    private List<String> wordsList = new ArrayList<String>();

    public WordsDatabase(WordService wordService) {
       wordsList.addAll(wordService.getWords());
    }

    public String getRandomWord() {
        // Interact with wordsList or wordService directly.
    }
}

An example service interface:

public interface WordService {
    List<String> getWords();
}

Now, in your test you can either mock an instance of WordService which returns an appropriate List of Strings, or provide some other controlled implementation.

If you control your classes' dependencies this kind of issue (largely) goes away. Look at examples of dependency injection for more information.

PA001
  • 451
  • 3
  • 12