#unittest

quetzop1@diasp.org

I just came across a little annoyance. A while ago, I wrote an algorithm that uses the part-of-speech (POS) model from Apache OpenNLP:

@RequiredArgsConstructor
public class Algorithm {
    private final POSModel posModel;

    public String doSomething(String text) {
        POSTaggerME tagger = new POSTaggerME(this.posModel);
        String[] posTags = tagger.tag(new String[] {text});

        // .. do some further stuff to compute "res"
        return res;
    }
}

At the time, I haven't written any unit tests for it because of time constraints and the interfaces weren't finalized yet. Now, I want to change something, so I also fix this and write a unit test for this class. However, this design isn't very test-friendly: In order to test doSomething, I need a valid POSModel instance. So, I either download the POS model from somewhere and load it before the test, or I figure out how to mock the constructor of POSTaggerME. Dependency injection of POSTaggerME, however, isn't an option because POSTaggerME::tag changes the object's internal state, so this computation isn't thread-safe.

I went with the second option because it seems easier and it has less dependencies. I searched online a while and the only solutions I found either didn't tackle my problem or proposed to use the PowerMock library. Introducing a new library to solve this issue seemed a bit wasteful and deep inside me, I knew there're some design flaws in this class.

So, I decided to actually use my brain and think about it a minute. And suddenly, the solution seemed so clear: Instead of creating a new POSTaggerME object via constructor, I could simply inject a factory that helps me to create POS taggers. This removes this class' dependency on OpenNLP's test-unfriendly classes altogether:

@RequiredArgsConstructor
public class Algorithm {
    private final PosTaggerFactory posTaggerFactory;

    public String doSomething(String text) {
        POSTaggerME tagger = this.posTaggerFactory.getTagger();
        String[] posTags = tagger.tag(new String[] {text});

        // .. do some further stuff to compute "res"
        return res;
    }
}

Then, I can easily mock the tagger factory to return a mocked tagger instance, which exactly returns what I want:

public class AlgorithmTest {
    @Test
    public void testDoSomething() {
        PosTaggerFactory posTaggerFactory = Mockito.mock(PosTaggerFactory.class);
        POSTaggerME posTagger = Mockito.mock(POSTaggerME.class);
        Mockito.when(posTagger.tag(new String[] {"Test"})).thenReturn(new String[] {"NN"});
        Mockito.when(this.posTaggerFactory.getTagger()).thenReturn(posTagger);
        Algorithm algorithm = new Algorithm(posTaggerFactory);

        String output = algorithm.doSomething("This is a test");
        Assertions.assertEquals("........", output);
    }
}

#programming #java #testing #test #unittest #mock