24 Likes
10 Comments
1 Shares
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);
}
}