2

I have a specific dictionary project that takes in user inputs and writes them to a database. The inputs (for now) are taken in through scanners (System.in) and I have multiple cases for example:

  1. Enter word and definition
  2. Enter synonyms
  3. Print dictionary

Things like this. How can I replicate user inputs with JUnit?

manfcas
  • 1,933
  • 7
  • 28
  • 47
Armando Xhimanki
  • 115
  • 1
  • 11

2 Answers2

3

This question is pretty broad; but the essential part to get is: to get the most out of JUnit, you should see as part of automation. Thus, you have to focus on removing anything that prevents that.

In other words: your application needs an interface that you can use to "feed" it with input. Meaning: your application should not contain any code that reads from System.in. Instead, your application should have a simple interface, like addWordWithDefinition(String word, String definition). And it should have methods to query such information later on.

And now writing a testcase is as simple as:

@Test
public void testAddAndGet() {
   String someWord = "whatever";
   String definition = "who cares";
   YourApplication underTest = new YourApplication(...
   underTest.addWordWithDefinition(someWord, definition);
   assertThat(underTest.getDefinitionFor(someWord), is(definition));

You go from there; you think about corner cases (like one, or both arguments are null), and so on. You try to absolute not test internals of your implementation. So, that ideally, when you change your "persistence" mechanism; your tests still work.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • I'd go with given, when, then as well ;) – Nickname0222022022 Aug 14 '16 at 08:43
  • 1
    @Sarseth That is way I am saying that his question is actually pretty broad. I didn't want to get into the "this is how you write unit tests in general" thing; as there simply too many options, paths, strategies. Would be too much for one answer; and probably too much for a person just starting unit testing, too. But you are right: this might be a good point in time for "newbies" to do more research on "how to write unit tests". – GhostCat Aug 14 '16 at 08:46
  • When you say simple interface what do you mean, Swing object or otherwise ? – Armando Xhimanki Aug 14 '16 at 09:24
  • I mean: the interface of your *dictionary* has **nothing** to do with user interfaces. As @davidhxxx is saying: these are different responsibilities. Your dictionary shouldn't know if the user is typing on some console; of if you are using a Swing-based UI, or if data is coming in via some restful HTTP call. – GhostCat Aug 14 '16 at 09:38
2

GhostCat is right. I would like completing its answer.

In your application, there are 2 distinct responsibilities :

  • capturing user requests for each one of your business cases(Enter word and definition, Enter synonyms, Print dictionary) : a user interface responsibility.
  • processing the logic : a logic responsibility.

Good design tries to avoid mixing responsibilities. So, you should create a class for :

  • capturing user requests. It captures the user input from the System.in and it dispatches data to the appropriate method of the processing class.
  • processing the logic. it is the logic class.

What is the advantage of it ?
The logic of your application is now in the class processing the logic.
In this way, you can concentrate unit test on this class to check that your application handle the logic as expected.

With a design like it, You don't need to worry about the scanner since it's a mature component which works : you don't need to unit test it works.

-- Edit : For answering your comment about how to realize an integration test with System.in

I looked the code.
First, with your actual code, you could not do an integration test with scanner which relies on multiple inputs captured because classic mocking tools as Mockito doesn't allow to do it (class is final).
You can stub the inputstream but as in the same method, you take multiple inputs (option selection + values), you cannot stubbed the two inputs.
So, we have to find a trick for bypassing it. A way to handle the problem is creating two ways of using your application : one with input entered one by one by user and another with all inputs entered in one time with delimiters for your unit test.

All is not operational but you can follow the principle.

The unit test will be like that :

@Test
public void printOptionWithAddedWordInDictionary() throws Exception {
  final ByteArrayInputStream inputStreamStubbed = new   ByteArrayInputStream("a|dog|super animal!".getBytes());
  InputReceiver inputReceiver = new InputReceiver(inputStreamStubbed, "\\|");
  inputReceiver.printOptions();
}

And as you see, you have two constructors now and two new private fields :

private InputStream inputStream;
private Scanner input;

public InputReceiver() {
  inputStream = System.in;
  scanner = new Scanner(inputStream);
}

  // for unit testing
 InputReceiver(InputStream inputStream, String delimiter) {
  this.inputStream = inputStream;
  scanner = new Scanner(inputStream);
  scanner.useDelimiter(delimiter);
}

The second is package private for testing.

davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • This is my project layout: I have a class called " Initializer " which has a field that is a " Dictionary " object which I have created. "Initializer" creates an "InputReceiver" object which displays the menu i have listed above. Depending on the input ( a, b,c) it calls upon the addWord(), addSynonym(),printAll() methods in another class called "inputHandler". This is the main portion of my logic. "InputHandler decides if a word deserves to be added, if it already exists, and if it is allowed to have synonyms. It also creates transitivity for synonms. – Armando Xhimanki Aug 14 '16 at 09:20
  • I want to create a JUnit test to go through a sample user interaction for example, Loop through RNG strings and create words and definition to auto-fill my tables for me. – Armando Xhimanki Aug 14 '16 at 09:22
  • Thank you for these precisions. You design seems good. So, you would like to do an integration test : a test which tests all the chain of the components from the InputReceiver until the real insertion, so without mocking behavior ? – davidxxx Aug 14 '16 at 09:27
  • exactly, an integration test – Armando Xhimanki Aug 14 '16 at 09:31
  • can i push to github and send you what i have so far? – Armando Xhimanki Aug 14 '16 at 09:33
  • Yes. It would be a good idea :) – davidxxx Aug 14 '16 at 09:34
  • https://github.com/Axhimanki/Dictionary – Armando Xhimanki Aug 14 '16 at 09:42
  • I did the begining With it, the follows should be easy. I will post it now – davidxxx Aug 14 '16 at 10:04
  • OK :) ill check soon – Armando Xhimanki Aug 14 '16 at 10:29
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/120906/discussion-between-armando-xhimanki-and-davidhxxx). – Armando Xhimanki Aug 14 '16 at 10:55
  • @Armando Xhimanki If i addressed your question, don't hesitate to accept it :) – davidxxx Aug 14 '16 at 13:00