Writing test case for Junit testing
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()
andaddHighlight()
throwingBadLocationException
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/orgetText()
) may also throwBadLocationException
, however thetry-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.
Global Dictator
Updated on July 23, 2022Comments
-
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 over 13 yearsCould you format the code so it's readable (and remove the line numbers)?
-
Ralph Sinsuat over 13 yearsIt must be a trick question, because you shouldn't unit test private methods.
-
-
Global Dictator over 13 yearsThanks 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 over 13 yearsDo 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 over 13 yearsI 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 over 13 yearsAnd 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 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 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 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 over 13 yearsHi 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 over 13 years@Global, strange. For us it worked. What do you precisely mean by "mvn instal didnt work"?
-
Global Dictator over 13 yearsI 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 over 13 years@Global, by "top level" do you mean that it is not inside the
<dependencies>
section? If so, move it there and retrymvn install
. -
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 over 13 yearsAlso, 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 over 13 years@Global, You can
import static org.easymock.EasyMock.*
to make the code work as it is, or you can add theEasyMock.
qualifier to the individual calls. Sorry, I should have mentioned that in the answer :-( -
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 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 over 13 yearsFinally 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 over 13 years@Global, glad to hear :-)
expect(mockDocument.getLength()).andReturn(1)
makes the mock document expect a single call togetLength()
, 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 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 over 13 yearspublic 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 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(mockDocument); EasyMock.expect(mockDocument.getLength()).andReturn(256); mockHighlighter = EasyMock.createMock(Highlighter.class); EasyMock.expect(mockComponent.getHighlighter()).andReturn(mockHighlighter); }
-
Global Dictator over 13 years@Test public void testSetLength() { MaxLength maxListener = new MaxLength(125); maxListener.decorate(textPane); } }
-
Global Dictator over 13 yearsThe 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 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 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 over 13 years@Global, the unit test
testSetLength
does not seem to test the methodhandle
you showed in your original post. So the mock setup is irrelevant for this test. And even if it were, you don't callEasyMock.replay
so the mocks are still in recording mode during the whole test. -
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 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 over 13 yearsHi 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 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 isJTextComponent
. -
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 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 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 over 13 years@Peter: Could you please advise what could I do here o fix this problem...Thanks
-
Global Dictator over 13 years@ Peter: Pleas see the update in my original question. Wld appreciate your comments..
-
Péter Török over 13 years@Global, the error message
Expectation failure on verify: getHighlighter(): expected: 1, actual: 0
means that the methodgetHighlighter()
was expected to be called once (as was configured in the last statement of yoursetUp()
method), but it was not called. Which is strange because in thehandle
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 insidehandle
. -
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 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???