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.
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
- 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.
- 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);
}
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.