Writing test case for Junit testing

10,616

Solution 1

I recommend using a mocking framework, such as EasyMock. Mocks allow you to configure dependencies with the desired behaviour for your tests. In your case, you need a mock DocumentEvent and ideally another one for component, which I guess is a class member.

The two aspects to unit testing

  • how to test, i.;e. the technical details of assembling the right set of objects in the right state required for the test to run properly (aka the _test fixture), and
  • what to test, i.e. the scenarios to validate.

How to test

Eclipse supports JUnit out of the box, so you may quickly generate new JUnit testcases (in Project Explorer context menu: New -> (Other ->) JUnit -> JUnit Test Case), then run it by clicking on the Run button.

Setting up the test fixture in your case would look something like this, using EasyMock (and assuming you can pass the component as a constructor parameter to your tested class):

private Component mockComponent;
private ClassUnderTest classUnderTest;
private DocumentEvent mockEvent;
private Document mockDocument;
private Highlighter mockHighlighter;

@Before
public void setUp() {
    mockComponent = createMock(Component.class);
    classUnderTest = new ClassUnderTest(mockComponent);
    mockEvent = createMock(DocumentEvent.class);
    mockDocument = createMock(Document.class);
    expect(mockEvent.getDocument()).andStubReturn(mockDocument);
    expect(mockDocument.getLength()).andReturn(1);
    mockHighlighter = createMock(Highlighter.class);
    expect(mockComponent.getHighlighter()).andReturn(mockHighlighter);
}

@Test
public void testEmptyText() {
    expect(mockDocument.getText(0, 1)).andStubReturn("");
    mockHighlighter.removeAllHighlights();
    replay(mockComponent, mockEvent, mockDocument, mockHighlighter);

    classUnderTest.handle(mockEvent);

    verify(mockComponent, mockEvent, mockDocument, mockHighlighter);
}

This test assumes that maxMessageSize is at least 1 by default - setting maxMessageSize up for the test is left to you as an exercise as the code snippet you published gives no clue for that.

What to test

The method you show gets text from the document associated with the event, then based on its length, it does different things. I would write at least the following unit tests for this:

  • empty document text with maxMessageSize == 0
  • empty document text with maxMessageSize > 0
  • nonempty document text with maxMessageSize == text.length()
  • nonempty document text with maxMessageSize > text.length()
  • nonempty document text with maxMessageSize < text.length() and addHighlight() throwing BadLocationException

Notes

  1. sensing the BadLocationException is a bit tricky, since all it produces is an output to stdout; luckily, you can easily reassign stdout via System.setOut. However, you may want to consider improving exception handling, at least by using a logging framework instead of printing to stdout.
  2. from the code it seems that other methods (such as removeAllHighlights() and/or getText()) may also throw BadLocationException, however the try-catch blocks are not well organized. I would consider adding more unit tests where those methods throw, and after that, refactoring the exception handling code.

Update

I thought there was something wrong that I was doing...Can you please provide me with the modified/corrected code please???

Your testSetLength method is not really testing anything - you need assert statements (and/or EasyMock verification) in order for your unit tests to actually verify some behaviour. However, it provides the missing clue for setting up the tested class. So I try to unify your two test methods to create one which is hopefully working (I am writing from memory, so I can't guarantee it will all compile and run perfectly at first try) :

  @Test 
  public void testEmptyText() { 
    // set up the test class with a specific max length
    classUnderTest = new MaxLength(125); 
    // this shall be called from inside decorate()
    mockDocument.addDocumentListener(classUnderTest); 
    // the mock document shall always return an empty text
    EasyMock.expect(mockDocument.getText(0, 1)).andStubReturn(""); 
    // we expect this to be called from inside handle()
    mockHighlighter.removeAllHighlights();
    // start replay mode
    EasyMock.replay(mockComponent, mockEvent, mockDocument, mockHighlighter); 
    // inject mock component into tested object
    maxListener.decorate(mockComponent); 

    // call the tested method
    classUnderTest.handle(mockEvent); 

    // verify that all expected calls to the mocks have been made    
    EasyMock.verify(mockComponent, mockEvent, mockDocument, mockHighlighter); 
  }

Solution 2

When you write a unit test, you try to test if (in this case) the method does what it is supposed to do. You should not look at the implementation and write your test from that. Instead, you should think about what inputs the method should be able to handle, and what should be the result (returned value and/or side effects) after the method has been called.

Then you should write one or more tests that calls the method with valid and and invalid inputs and make the test confirm that the results matched what you thought would happen.

This was a short and incomplete description, read more at Wikipedia and junit.org.

Here is an old (2005) but working guide to JUnit in Eclipse.

Share:
10,616
Global Dictator
Author by

Global Dictator

Updated on July 23, 2022

Comments

  • Global Dictator
    Global Dictator almost 2 years

    As a developer, I'm a newbie to Unit testing and have a requirement to write a test case to unit test the following code. Could somebody help me here and also give me some pointers on how to write unit tests in eclipse.

    private void handle(final DocumentEvent e) { 
        Document doc = e.getDocument(); 
        try { 
           String text = e.getDocument().getText(0, doc.getLength()); 
    
           if (text.length() >= maxMessageSize) { 
                try { 
                   component.getHighlighter()
                            .addHighlight(maxMessageSize, text.length() + 1, painter); 
                } catch (BadLocationException ex) { 
                   System.out.println(ex.getMessage()); 
                } 
           } else { 
                component.getHighlighter().removeAllHighlights(); 
           } 
        } catch (BadLocationException e1) { 
           System.out.println(e1.getMessage()); 
        } 
    } 
    

    Thanks


    Update

    For some reason when I running the test case, I'm not getting any coverage at all. Am I doing something wrong here?? Further researching suggests that I need to use test.perform() method to call the method I want to test.. Is that correct?? Can you please suggest something?? Here is the code:

    public class TestMaxLength {
      static final int maxMessageSize = 125;
      JTextPane textPane = new JTextPane();
      //***EasyMock varibles****
      private JTextComponent mockComponent;
      private MaxLength classUnderTest;
      private DocumentEvent mockEvent;
      private Document mockDocument;
      private Highlighter mockHighlighter;
    
      @Before public void setUp() {
        mockComponent = EasyMock.createMock(JTextComponent.class);
        mockEvent = EasyMock.createMock(DocumentEvent.class); 
        mockDocument = EasyMock.createMock(Document.class); 
        EasyMock.expect(mockEvent.getDocument()).andStubReturn(mockDocument);
        EasyMock.expect(mockDocument.getLength()).andReturn(256); 
        mockHighlighter = EasyMock.createMock(Highlighter.class); 
        EasyMock.expect(mockComponent.getHighlighter()).andReturn(mockHighlighter);
      }
    
      @Test public void testSetLength() { 
        MaxLength maxListener = new MaxLength(125); 
        maxListener.decorate(textPane);
      }
    
      @Test 
      public void testEmptyText() { 
        EasyMock.expect(mockDocument.getText(0, 1)).andStubReturn(""); 
        mockHighlighter.removeAllHighlights(); 
        EasyMock.replay(mockComponent, mockEvent, mockDocument, mockHighlighter);      
        classUnderTest.handle(mockEvent);      
        EasyMock.verify(mockComponent, mockEvent, mockDocument, mockHighlighter); 
      }     
    }
    

    The decorate(JtextComponent jComponent) method is present in the class to be tested (MaxLength) and is defined as :

    public final void decorate(final JTextComponent c) {
      //TODO throw exception if already decorating
      this.component = c;
      component.getDocument().addDocumentListener(this);
    }
    
    #

    UPDATE:

    @Peter: Managed to find out that it is not the Component class that is the problem but instead I needed asm (http://forge.ow2.org/projects/asm). I've also change the code to combine the 2 methods into 1 method:

    public void testEmptyText() 
    { 
    maxSizeListener.decorate(mockComponent);
    //mockHighlighter.removeAllHighlights(); 
    EasyMock.replay(mockComponent, mockEvent, mockDocument, mockHighlighter); 
    maxSizeListener.handle(mockEvent); 
    EasyMock.verify(mockComponent, mockEvent, mockDocument, mockHighlighter); 
    } 
    

    But now I'm having a different error on verify:

    java.lang.AssertionError: 
    Expectation failure on verify:
    getHighlighter(): expected: 1, actual: 0
    at org.easymock.internal.MocksControl.verify(MocksControl.java:184)
    at org.easymock.EasyMock.verify(EasyMock.java:2038)
    at net.TestMaxLength.testEmptyText(TestMaxLength.java:98)
    

    This is caused when executing EasyMock.verify() statement on mockComponent.

    • Sam Dolan
      Sam Dolan over 13 years
      Could you format the code so it's readable (and remove the line numbers)?
    • Ralph Sinsuat
      Ralph Sinsuat over 13 years
      It must be a trick question, because you shouldn't unit test private methods.
  • Global Dictator
    Global Dictator over 13 years
    Thanks for this. I'm using Cobertura tool to calculate the line and branch coverage and the line numbers were the indications where the code is was not tested. Also, the private accessor/visibility for the method was a mistaken and its been changed to public now. Could somebody give me a sample test case for this piece of code. Would be much appreciated. Thanks
  • Peter Jaric
    Peter Jaric over 13 years
    Do you mean some skeleton code where you can fill in the specifics? Because, as I said, you can not write a test if you don't know what behavior it should test.
  • Péter Török
    Péter Török over 13 years
    I strongly disagree. You describe black box testing, while unit testing is white-box testing - you do see the code to be tested, as it is (ideally) your own code! It is another matter that you should think about what contract the method is supposed to fulfill, and write test cases to verify that.
  • Peter Jaric
    Peter Jaric over 13 years
    And let me add this: don't write a test just to increase your coverage if the test doesn't really test your code. That would be false test coverage indeed.
  • Peter Jaric
    Peter Jaric over 13 years
    @Péter Török: If you wrote the test yourself, that is true of course. But I am quite sure we at SO didn't write the code above, and hence we could only write tests that confirm that the code is working if we get no indication of its purpose. And consider test driven development, where unit testing is at the core. There you don't even have the code when you write the test.
  • Andrzej Doyle
    Andrzej Doyle over 13 years
    @Péter Török - I know where you're coming from, but you should definitely start black-box. The goal with a unit test after all is often not so much to ensure that the code works right now, but rather to have a test in place to check that future, modified versions of the code will continue working. If you use your white-box view to say "oh, I know this can never be a problem because of the implementation details here so I won't test it" then what happens when someone refactors the class?
  • Péter Török
    Péter Török over 13 years
    @Andrzej, @Peter: I see your point, and I agree that one should not test only what the code explicitly does right now. That's why I mentioned testing against contract above. And it is true that in pure TDD one writes test first, then the code. However, in testing legacy code (as I believe is the case in this question), you can't avoid the fact that the code is already written, and often, lacking any useful documentation, you must deduce the supposed "contract" from the code itself.
  • Global Dictator
    Global Dictator over 13 years
    Hi Péter Török, Could you please give me some instructions to install EasyMock manually. I tried adding this dependency in the POM file as instructed at the Easymock documentation page and then tried running mvn instal but it didnt work. Was I doing something wrong there?? In which case could you let me know how to get this dependecny from teh maven repo.. Also, I've downloaded the jar file and was wondering how to install Easymock that way. Thanks.
  • Péter Török
    Péter Török over 13 years
    @Global, strange. For us it worked. What do you precisely mean by "mvn instal didnt work"?
  • Global Dictator
    Global Dictator over 13 years
    I added the following code to the project POM file at top level: <dependency> <groupId>org.easymock</groupId> <artifactId>easymock</artifactId> <version>3.0</version> <scope>test</scope> </dependency> There were no further instructions on the EasyMock documentation, hence I tried downloading the dependency from the Maven repo by running this command.. I've just started using Maven couple of weeks ago, so didnt know what else I needed to do. Thx.
  • Péter Török
    Péter Török over 13 years
    @Global, by "top level" do you mean that it is not inside the <dependencies> section? If so, move it there and retry mvn install.
  • Global Dictator
    Global Dictator over 13 years
    @Péter No, I did include the easymock in the <dependencies> section. How can we check that this dependency is actually present and available to us for download?
  • Global Dictator
    Global Dictator over 13 years
    Also, I'm trying to imported the EasyMock by having this statement import org.easymock.EasyMock.*; but when using your method (createMock()) is throwing an error..
  • Péter Török
    Péter Török over 13 years
    @Global, You can import static org.easymock.EasyMock.* to make the code work as it is, or you can add the EasyMock. qualifier to the individual calls. Sorry, I should have mentioned that in the answer :-(
  • Péter Török
    Péter Török over 13 years
    @Global, for verifying the dependency, use the maven-dependency-plugin. I don't have much experience with it but try mvn dependency:resolve -Dsilent=true.
  • Global Dictator
    Global Dictator over 13 years
    @Peter, do you mean that if I add this import statement, I dont have to add EasyMock in the POM file. I'm sorry but i'm really confused and totally lost as to what needs to be done to get this to work. Also, what are the prerequisites to make this import statement to work? Do I need to play with the jars or something?? Please advise...
  • Global Dictator
    Global Dictator over 13 years
    Finally got EasyMock working. Just used EasyMock.createMock() with the import statement at the top. Looking at your code, could you please briefly explain what each statement in the code is doing. I know little about what expect(), Verify(), replay() methods do but what about andStubReturn() and return() method?? Thanks
  • Péter Török
    Péter Török over 13 years
    @Global, glad to hear :-) expect(mockDocument.getLength()).andReturn(1) makes the mock document expect a single call to getLength(), and return 1 from that call. andStubReturn() makes the mock return the specified value from any number of subsequent calls to that method. See the section Specifying Return Values in the EasyMock documentation for more details.
  • Global Dictator
    Global Dictator over 13 years
    @Peter: For some reason when I running the test case, I'm not getting any coverage at all. Am I doing something wrong here?? Further researching suggests that I need to use test.perform() method to call the method I want to test.. Is that correct?? Can you please suggest soemthing?? Here is the code:
  • Global Dictator
    Global Dictator over 13 years
    public class TestMaxLength { static final int maxMessageSize = 125; JTextPane textPane = new JTextPane(); //***EasyMock varibles**** private JTextComponent mockComponent; private MaxLength classUnderTest; private DocumentEvent mockEvent; private Document mockDocument; private Highlighter mockHighlighter;
  • Global Dictator
    Global Dictator over 13 years
    @Before public void setUp() { mockComponent = EasyMock.createMock(JTextComponent.class); mockEvent = EasyMock.createMock(DocumentEvent.class); mockDocument = EasyMock.createMock(Document.class); EasyMock.expect(mockEvent.getDocument()).andStubReturn(mockD‌​ocument); EasyMock.expect(mockDocument.getLength()).andReturn(256); mockHighlighter = EasyMock.createMock(Highlighter.class); EasyMock.expect(mockComponent.getHighlighter()).andReturn(mo‌​ckHighlighter); }
  • Global Dictator
    Global Dictator over 13 years
    @Test public void testSetLength() { MaxLength maxListener = new MaxLength(125); maxListener.decorate(textPane); } }
  • Global Dictator
    Global Dictator over 13 years
    The decorate(JtextComponent jComponent) method is present in the class to be tested (MaxLength) and is defines as : public final void decorate(final JTextComponent c) { //TODO throw exception if already decorating this.component = c; component.getDocument().addDocumentListener(this); }
  • Péter Török
    Péter Török over 13 years
    @Global, I added the content of your comments as an update to your original post - please do so in the future, as code is very badly readable in comments :-) Do I understand correctly that your unit tests actually run, and you get the green bar meaning all tests pass, but you get no coverage output?
  • Péter Török
    Péter Török over 13 years
    @Global, I think the coverage configuration is distinct from the unit test configuration, but I have never used coverage tools in Eclipse so I can't give you much concrete help with this. Have you rebuilt your project after switching Cobertura on? IMHO the coverage tool (if configured properly) should work fine with vanilla JUnit test methods. I have never heard about that test.perform() method and I doubt it would have anything to do with your coverage setup - could you give a reference to the source of this information?
  • Péter Török
    Péter Török over 13 years
    @Global, the unit test testSetLength does not seem to test the method handle you showed in your original post. So the mock setup is irrelevant for this test. And even if it were, you don't call EasyMock.replay so the mocks are still in recording mode during the whole test.
  • Global Dictator
    Global Dictator over 13 years
    @Peter, I thought there was something wrong that I was doing...Can you please provide me with the modified/corrected code please???
  • Global Dictator
    Global Dictator over 13 years
    @Peter, I forgot to mention that I separated the test methods into 2 methods, one that uses the mock objects and other without any mock objects. I have now added both the methods in the code above..Cld you have a look and let me know what am I doing wrong..
  • Global Dictator
    Global Dictator over 13 years
    Hi Peter, thanks for this. But I'm getting a java.lang.NoClassDefFoundError when trying to create a mock Component class. mockComponent = EasyMock.createMock(Component.class); Rest all the createMock statements are working fine. What do you suggest could be causing this!!! Thanks
  • Péter Török
    Péter Török over 13 years
    @Global, your code above contains mockComponent = EasyMock.createMock(JTextComponent.class), isn't it working? In my first example, Component was just a placeholder name for a class whose real name I didn't know - sorry for not making this clear. I assume from your second code sample that the real type is JTextComponent.
  • Global Dictator
    Global Dictator over 13 years
    @Peter: Yes, thats right that I'm using JTextComponent but am not able to create the mock object of it.. Its giving me java.lang.NoClassDefFoundError when running the Junit test. Could you please help!!!
  • Péter Török
    Péter Török over 13 years
    @Global, sounds like a classpath problem. Make sure that your test classes also "see" the jar containing JTextComponent.class at both compile and runtime.
  • Global Dictator
    Global Dictator over 13 years
    @Peter: How can I achieve to make the test classes see the JTextComponent class?? I thought that all Swing text components inherit from the same superclass, JTextComponent,which is made available as part of the java.Swing package.... Please advise..
  • Global Dictator
    Global Dictator over 13 years
    @Peter: Could you please advise what could I do here o fix this problem...Thanks
  • Global Dictator
    Global Dictator over 13 years
    @ Peter: Pleas see the update in my original question. Wld appreciate your comments..
  • Péter Török
    Péter Török over 13 years
    @Global, the error message Expectation failure on verify: getHighlighter(): expected: 1, actual: 0 means that the method getHighlighter() was expected to be called once (as was configured in the last statement of your setUp() method), but it was not called. Which is strange because in the handle method you showed above, component.getHighlighter() is supposed to be called always (except if an exception was thrown within the method). I suggest you debug the test to figure out what is actually happening inside handle.
  • Péter Török
    Péter Török over 13 years
    @Global, hope that helps. Note that I won't be present on SO very often in the coming weeks, so don't be disappointed if you don't get quick answers from me...
  • Global Dictator
    Global Dictator over 13 years
    @Peter: It was really helpful. Appreciate that!! One thing that I'm not sure is that if mockComponent.getHighlighter() is called in handle() then, so I need to add expect for method getHighlighter() on mockComponent too???