caiohamamura
caiohamamura

Reputation: 2738

Unit testing (java): Will I have to write public methods to expose data to unit tests?

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.

Upvotes: 4

Views: 1222

Answers (4)

PA001
PA001

Reputation: 451

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.

Upvotes: 1

Krease
Krease

Reputation: 16215

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

Upvotes: 2

Jo&#227;o Esperancinha
Jo&#227;o Esperancinha

Reputation: 1169

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.

Upvotes: 1

Davide Lorenzo MARINO
Davide Lorenzo MARINO

Reputation: 26926

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.

Upvotes: 2

Related Questions