4

I have a utility class called StringProcessor. The breakLongWords() method in it, adds zero-width spaces to the input whenever a sequence of characters lack white space for a predefined length:

public class StringProcessor {

    private static final int WORD_MAX_LENGTH = 40;

    public String breakLongWords(CharSequence input) {
        // add a zero-width space character after a word
        // if its length is greater than WORD_MAX_LENGTH and doesn't have any space in it
    }
}

The static field WORD_MAX_LENGTH is an implementation detail and should not be exposed to other classes (including test classes).

Now, how can I test the edge case in JUnit without accessing WORD_MAX_LENGTH? For example:

@Test
public void breakLongWords_EdgeCase() {
    String brokenText = stringProcessor.breakLongWords
            ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // its length should be = WORD_MAX_LENGTH 

    assertEquals(41, brokenText.length()); // 41 (WORD_MAX_LENGTH + 1) is also hard-coded here
}
Mahozad
  • 18,032
  • 13
  • 118
  • 133
  • 1
    If the test is not supposed to know `WORD_MAX_LENGTH` or even the value of it, then it's not possible to write such a unit test - it's simply information that the test needs, otherwise there's no way for it to know what to test... – Jesper Aug 20 '18 at 21:11
  • What is your reason for not making `WORD_MAX_LENGTH` public? What harm could other parts of your program do by knowing its value? – Dawood ibn Kareem Aug 20 '18 at 21:13
  • Made it package private. I surrender! – Mahozad Aug 20 '18 at 21:16
  • It seems that this constant is actually part of the specification of what your code should do, so stating that it must be private makes no sense. [First produce a clear and precise specification ](https://stackoverflow.com/a/53757321/545127). – Raedwald Dec 13 '18 at 09:18

3 Answers3

4

Usually such fields are made "package-private" (that is with no access modifier) and unit tests are placed on the same package.
So constants are visible for tests and classes from the same package and hidden for others.

Mahozad
  • 18,032
  • 13
  • 118
  • 133
udalmik
  • 7,838
  • 26
  • 40
2

You have two choices:

  1. Expose WORD_MAX_LENGTH to your tests.

  2. Maintain a second constant for tests.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
  • I would argue that option 2 is bad practice. If you have a variable/method you wish not to expose, it makes no sense testing it. As far as design goes, I would recommend adding a 2nd parameter to the signature (perhaps javadocing that the single parameter signature uses some default value) – Roy Shahaf Aug 20 '18 at 21:04
  • @RoyShahaf I completely agree. I prefer option 1. You can reduce the exposure by following the suggestion in udalmik's answer. – Code-Apprentice Aug 20 '18 at 21:09
0

I might redesign the StringProcessor and the test.

public class StringProcessor {

    private static final int WORD_MAX_LENGTH = 40;

    public boolean stringLengthIsSafe(CharSequence input){
        if(input.length()>WORD_MAX_LENGTH){
           return false;
        }
        return true;
    }

    public String breakLongWords(CharSequence input) {
        // add a zero-width space character after a word
        // if its length is greater than WORD_MAX_LENGTH and doesn't have any space in it
    }
}

And then test on the boolean.

@Test
public void breakLongWords_EdgeCase() {
    boolean safeString = stringProcessor.stringLengthIsSafe
            ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // its length should be = WORD_MAX_LENGTH 

    assertEquals(true, safeString); // 41 (WORD_MAX_LENGTH + 1) is also hard-coded here
}

In your final implementation, breakLongWords could implement stringLengthIsSafe before attempting any manipulation of the CharSequence.

J E Carter II
  • 1,436
  • 1
  • 22
  • 39
  • I would redesign it differently but the problem is definitely the design of this class which is making this trickier to test. The problem is that you have a magic number. The solution is injecting that via a constructor and having a default constructor that uses the constant. That way, you can control the number from the outside; like in your test. – Jilles van Gurp Aug 21 '18 at 08:46
  • You're correct on the magic number. Simply for brevity, it's acceptable but should be externalized. The primary goal of my take on this was to make sure the test follows the single responsibility goal. The test doesn't care how the evaluation is made, simply that the tested class provides the expected evaluation. – J E Carter II Aug 21 '18 at 13:10