- •Practical Unit Testing with JUnit and Mockito
- •Table of Contents
- •About the Author
- •Acknowledgments
- •Preface
- •Preface - JUnit
- •Part I. Developers' Tests
- •Chapter 1. On Tests and Tools
- •1.1. An Object-Oriented System
- •1.2. Types of Developers' Tests
- •1.2.1. Unit Tests
- •1.2.2. Integration Tests
- •1.2.3. End-to-End Tests
- •1.2.4. Examples
- •1.2.5. Conclusions
- •1.3. Verification and Design
- •1.5. Tools Introduction
- •Chapter 2. Unit Tests
- •2.1. What is a Unit Test?
- •2.2. Interactions in Unit Tests
- •2.2.1. State vs. Interaction Testing
- •2.2.2. Why Worry about Indirect Interactions?
- •Part II. Writing Unit Tests
- •3.2. Class To Test
- •3.3. Your First JUnit Test
- •3.3.1. Test Results
- •3.4. JUnit Assertions
- •3.5. Failing Test
- •3.6. Parameterized Tests
- •3.6.1. The Problem
- •3.6.2. The Solution
- •3.6.3. Conclusions
- •3.7. Checking Expected Exceptions
- •3.8. Test Fixture Setting
- •3.8.1. Test Fixture Examples
- •3.8.2. Test Fixture in Every Test Method
- •3.8.3. JUnit Execution Model
- •3.8.4. Annotations for Test Fixture Creation
- •3.9. Phases of a Unit Test
- •3.10. Conclusions
- •3.11. Exercises
- •3.11.1. JUnit Run
- •3.11.2. String Reverse
- •3.11.3. HashMap
- •3.11.4. Fahrenheits to Celcius with Parameterized Tests
- •3.11.5. Master Your IDE
- •Templates
- •Quick Navigation
- •Chapter 4. Test Driven Development
- •4.1. When to Write Tests?
- •4.1.1. Test Last (AKA Code First) Development
- •4.1.2. Test First Development
- •4.1.3. Always after a Bug is Found
- •4.2. TDD Rhythm
- •4.2.1. RED - Write a Test that Fails
- •How To Choose the Next Test To Write
- •Readable Assertion Message
- •4.2.2. GREEN - Write the Simplest Thing that Works
- •4.2.3. REFACTOR - Improve the Code
- •Refactoring the Tests
- •Adding Javadocs
- •4.2.4. Here We Go Again
- •4.3. Benefits
- •4.4. TDD is Not Only about Unit Tests
- •4.5. Test First Example
- •4.5.1. The Problem
- •4.5.2. RED - Write a Failing Test
- •4.5.3. GREEN - Fix the Code
- •4.5.4. REFACTOR - Even If Only a Little Bit
- •4.5.5. First Cycle Finished
- •‘The Simplest Thing that Works’ Revisited
- •4.5.6. More Test Cases
- •But is It Comparable?
- •Comparison Tests
- •4.6. Conclusions and Comments
- •4.7. How to Start Coding TDD
- •4.8. When not To Use Test-First?
- •4.9. Should I Follow It Blindly?
- •4.9.1. Write Good Assertion Messages from the Beginning
- •4.9.2. If the Test Passes "By Default"
- •4.10. Exercises
- •4.10.1. Password Validator
- •4.10.2. Regex
- •4.10.3. Booking System
- •Chapter 5. Mocks, Stubs, Test Spies
- •5.1. Introducing Mockito
- •5.1.1. Creating Test Doubles
- •5.1.2. Expectations
- •5.1.3. Verification
- •5.1.4. Conclusions
- •5.2. Types of Test Double
- •5.2.1. Code To Be Tested with Test Doubles
- •5.2.2. The Dummy Object
- •5.2.3. Test Stub
- •5.2.4. Test Spy
- •5.2.5. Mock
- •5.3. Putting it All Together
- •5.4. Example: TDD with Test Doubles
- •5.4.2. The Second Test: Send a Message to Multiple Subscribers
- •Refactoring
- •5.4.3. The Third Test: Send Messages to Subscribers Only
- •5.4.4. The Fourth Test: Subscribe More Than Once
- •Mockito: How Many Times?
- •5.4.5. The Fifth Test: Remove a Subscriber
- •5.4.6. TDD and Test Doubles - Conclusions
- •More Test Code than Production Code
- •The Interface is What Really Matters
- •Interactions Can Be Tested
- •Some Test Doubles are More Useful than Others
- •5.5. Always Use Test Doubles… or Maybe Not?
- •5.5.1. No Test Doubles
- •5.5.2. Using Test Doubles
- •No Winner So Far
- •5.5.3. A More Complicated Example
- •5.5.4. Use Test Doubles or Not? - Conclusion
- •5.6. Conclusions (with a Warning)
- •5.7. Exercises
- •5.7.1. User Service Tested
- •5.7.2. Race Results Enhanced
- •5.7.3. Booking System Revisited
- •5.7.4. Read, Read, Read!
- •Part III. Hints and Discussions
- •Chapter 6. Things You Should Know
- •6.1. What Values To Check?
- •6.1.1. Expected Values
- •6.1.2. Boundary Values
- •6.1.3. Strange Values
- •6.1.4. Should You Always Care?
- •6.1.5. Not Only Input Parameters
- •6.2. How to Fail a Test?
- •6.3. How to Ignore a Test?
- •6.4. More about Expected Exceptions
- •6.4.1. The Expected Exception Message
- •6.4.2. Catch-Exception Library
- •6.4.3. Testing Exceptions And Interactions
- •6.4.4. Conclusions
- •6.5. Stubbing Void Methods
- •6.6. Matchers
- •6.6.1. JUnit Support for Matcher Libraries
- •6.6.2. Comparing Matcher with "Standard" Assertions
- •6.6.3. Custom Matchers
- •6.6.4. Advantages of Matchers
- •6.7. Mockito Matchers
- •6.7.1. Hamcrest Matchers Integration
- •6.7.2. Matchers Warning
- •6.8. Rules
- •6.8.1. Using Rules
- •6.8.2. Writing Custom Rules
- •6.9. Unit Testing Asynchronous Code
- •6.9.1. Waiting for the Asynchronous Task to Finish
- •6.9.2. Making Asynchronous Synchronous
- •6.9.3. Conclusions
- •6.10. Testing Thread Safe
- •6.10.1. ID Generator: Requirements
- •6.10.2. ID Generator: First Implementation
- •6.10.3. ID Generator: Second Implementation
- •6.10.4. Conclusions
- •6.11. Time is not on Your Side
- •6.11.1. Test Every Date (Within Reason)
- •6.11.2. Conclusions
- •6.12. Testing Collections
- •6.12.1. The TDD Approach - Step by Step
- •6.12.2. Using External Assertions
- •Unitils
- •Testing Collections Using Matchers
- •6.12.3. Custom Solution
- •6.12.4. Conclusions
- •6.13. Reading Test Data From Files
- •6.13.1. CSV Files
- •6.13.2. Excel Files
- •6.14. Conclusions
- •6.15. Exercises
- •6.15.1. Design Test Cases: State Testing
- •6.15.2. Design Test Cases: Interactions Testing
- •6.15.3. Test Collections
- •6.15.4. Time Testing
- •6.15.5. Redesign of the TimeProvider class
- •6.15.6. Write a Custom Matcher
- •6.15.7. Preserve System Properties During Tests
- •6.15.8. Enhance the RetryTestRule
- •6.15.9. Make an ID Generator Bulletproof
- •Chapter 7. Points of Controversy
- •7.1. Access Modifiers
- •7.2. Random Values in Tests
- •7.2.1. Random Object Properties
- •7.2.2. Generating Multiple Test Cases
- •7.2.3. Conclusions
- •7.3. Is Set-up the Right Thing for You?
- •7.4. How Many Assertions per Test Method?
- •7.4.1. Code Example
- •7.4.2. Pros and Cons
- •7.4.3. Conclusions
- •7.5. Private Methods Testing
- •7.5.1. Verification vs. Design - Revisited
- •7.5.2. Options We Have
- •7.5.3. Private Methods Testing - Techniques
- •Reflection
- •Access Modifiers
- •7.5.4. Conclusions
- •7.6. New Operator
- •7.6.1. PowerMock to the Rescue
- •7.6.2. Redesign and Inject
- •7.6.3. Refactor and Subclass
- •7.6.4. Partial Mocking
- •7.6.5. Conclusions
- •7.7. Capturing Arguments to Collaborators
- •7.8. Conclusions
- •7.9. Exercises
- •7.9.1. Testing Legacy Code
- •Part IV. Listen and Organize
- •Chapter 8. Getting Feedback
- •8.1. IDE Feedback
- •8.1.1. Eclipse Test Reports
- •8.1.2. IntelliJ IDEA Test Reports
- •8.1.3. Conclusion
- •8.2. JUnit Default Reports
- •8.3. Writing Custom Listeners
- •8.4. Readable Assertion Messages
- •8.4.1. Add a Custom Assertion Message
- •8.4.2. Implement the toString() Method
- •8.4.3. Use the Right Assertion Method
- •8.5. Logging in Tests
- •8.6. Debugging Tests
- •8.7. Notifying The Team
- •8.8. Conclusions
- •8.9. Exercises
- •8.9.1. Study Test Output
- •8.9.2. Enhance the Custom Rule
- •8.9.3. Custom Test Listener
- •8.9.4. Debugging Session
- •Chapter 9. Organization Of Tests
- •9.1. Package for Test Classes
- •9.2. Name Your Tests Consistently
- •9.2.1. Test Class Names
- •Splitting Up Long Test Classes
- •Test Class Per Feature
- •9.2.2. Test Method Names
- •9.2.3. Naming of Test-Double Variables
- •9.3. Comments in Tests
- •9.4. BDD: ‘Given’, ‘When’, ‘Then’
- •9.4.1. Testing BDD-Style
- •9.4.2. Mockito BDD-Style
- •9.5. Reducing Boilerplate Code
- •9.5.1. One-Liner Stubs
- •9.5.2. Mockito Annotations
- •9.6. Creating Complex Objects
- •9.6.1. Mummy Knows Best
- •9.6.2. Test Data Builder
- •9.6.3. Conclusions
- •9.7. Conclusions
- •9.8. Exercises
- •9.8.1. Test Fixture Setting
- •9.8.2. Test Data Builder
- •Part V. Make Them Better
- •Chapter 10. Maintainable Tests
- •10.1. Test Behaviour, not Methods
- •10.2. Complexity Leads to Bugs
- •10.3. Follow the Rules or Suffer
- •10.3.1. Real Life is Object-Oriented
- •10.3.2. The Non-Object-Oriented Approach
- •Do We Need Mocks?
- •10.3.3. The Object-Oriented Approach
- •10.3.4. How To Deal with Procedural Code?
- •10.3.5. Conclusions
- •10.4. Rewriting Tests when the Code Changes
- •10.4.1. Avoid Overspecified Tests
- •10.4.2. Are You Really Coding Test-First?
- •10.4.3. Conclusions
- •10.5. Things Too Simple To Break
- •10.6. Conclusions
- •10.7. Exercises
- •10.7.1. A Car is a Sports Car if …
- •10.7.2. Stack Test
- •Chapter 11. Test Quality
- •11.1. An Overview
- •11.2. Static Analysis Tools
- •11.3. Code Coverage
- •11.3.1. Line and Branch Coverage
- •11.3.2. Code Coverage Reports
- •11.3.3. The Devil is in the Details
- •11.3.4. How Much Code Coverage is Good Enough?
- •11.3.5. Conclusion
- •11.4. Mutation Testing
- •11.4.1. How does it Work?
- •11.4.2. Working with PIT
- •11.4.3. Conclusions
- •11.5. Code Reviews
- •11.5.1. A Three-Minute Test Code Review
- •Size Heuristics
- •But do They Run?
- •Check Code Coverage
- •Conclusions
- •11.5.2. Things to Look For
- •Easy to Understand
- •Documented
- •Are All the Important Scenarios Verified?
- •Run Them
- •Date Testing
- •11.5.3. Conclusions
- •11.6. Refactor Your Tests
- •11.6.1. Use Meaningful Names - Everywhere
- •11.6.2. Make It Understandable at a Glance
- •11.6.3. Make Irrelevant Data Clearly Visible
- •11.6.4. Do not Test Many Things at Once
- •11.6.5. Change Order of Methods
- •11.7. Conclusions
- •11.8. Exercises
- •11.8.1. Clean this Mess
- •Appendix A. Automated Tests
- •A.1. Wasting Your Time by not Writing Tests
- •A.1.1. And what about Human Testers?
- •A.1.2. One More Benefit: A Documentation that is Always Up-To-Date
- •A.2. When and Where Should Tests Run?
- •Appendix B. Running Unit Tests
- •B.1. Running Tests with Eclipse
- •B.1.1. Debugging Tests with Eclipse
- •B.2. Running Tests with IntelliJ IDEA
- •B.2.1. Debugging Tests with IntelliJ IDEA
- •B.3. Running Tests with Gradle
- •B.3.1. Using JUnit Listeners with Gradle
- •B.3.2. Adding JARs to Gradle’s Tests Classpath
- •B.4. Running Tests with Maven
- •B.4.1. Using JUnit Listeners and Reporters with Maven
- •B.4.2. Adding JARs to Maven’s Tests Classpath
- •Appendix C. Test Spy vs. Mock
- •C.1. Different Flow - and Who Asserts?
- •C.2. Stop with the First Error
- •C.3. Stubbing
- •C.4. Forgiveness
- •C.5. Different Threads or Containers
- •C.6. Conclusions
- •Appendix D. Where Should I Go Now?
- •Bibliography
- •Glossary
- •Index
- •Thank You!
Chapter 7. Points of Controversy
Listing 7.17. Testing of the redesigned MySut class
public class MySutTest {
@Test
public void testMyMethod() {
MyCollaborator collaborator = mock(MyCollaborator.class); MySut sut = new MySut(collaborator);
// normal Mockito stubbing/test spying test
}
}
The updated constructor of the MySut class allows for straightforward injection of a collaborator test double.
The test in Listing 7.17 holds no surprises for us. Replacing the collaborator of sut is simply a matter of passing the appropriate test double to its constructor.
As this example demonstrates, a complicated case can be turned into something far simpler (from a testing point of view) by the redesign of the SUT. If it is possible to replace the cumbersome creation of objects (involving either the new operator or static factory methods) with a dependency injection, then testing becomes a trivial task.
Even so, there are some downsides – or, at least, some issues we should be aware of:
•production code gets altered to meet the requirements of the tests themselves (isn’t this a case of the tail wagging the dog?),
•such a redesign breaks existing clients (it truly is a redesign, not a refactoring),
•you need to write some additional code.
On the "plus" side, there are two important things:
•the design of production code is improved (myMethod() can take care of its business task, and need not occupy itself with objects creation),
•tests are very simple to write (which also means they are easy to understand and maintain).
After we have discussed all of the possible options, some conclusions will be drawn, but even now, I would like to encourage you to use this technique, because it addresses the real cause of the pain, not merely its symptoms. By redesigning your production code you make it better, and you get "testability" as a side effect.
7.6.3. Refactor and Subclass
In some cases the redesign discussed in the previous section is not feasible. For example, it might be the case that we cannot afford to break a client that uses the old API. In such a case, we need to find another way to make the class more testable. This can be done by a certain refactoring.
The pattern presented in this section is known by many names. [meszaros2007] calls this pattern a Test-Specific Subclass, while [feathers2004] uses the name subclass and override.
164
Chapter 7. Points of Controversy
Listing 7.18 shows a slightly changed version of the original MySut class.
Listing 7.18. Refactored version of the MySut class
public class MyRefactoredSut {
public void myMethod() {
MyCollaborator collaborator = createCollaborator();
// some behaviour worth testing here which uses collaborator
}
// method extracted to facilitate testing protected MyCollaborator createCollaborator() {
return new MyCollaborator();
}
}
Instead of invoking the new operator directly there is a call to a new method.
I strongly recommend adding a short comment to the extracted method, so it is clear that it has been created for the purpose of conducting unit tests.
The extracted method has a default access modifier, …and not very impressive content.
As for the access modifier of the extracted createCollaborator() method, we can choose between making it protected or using default access protection (so-called "package-private"). This has some impact on our test classes. If we keep them in the same package as the production code, then the default access will be good enough. If they reside in different packages, then we will need to make the access less restrictive (protected). Some information on the organization of test packages is given in Section 9.1.
The change introduced to the original class is minimal. All we did is a simple "extract method" refactoring. At first it might seem useless, but in fact it opens up a new testing possibility. Listing 7.19 shows a test which takes advantage of this minor refactoring.
Listing 7.19. Test of the refactored version of the MySut class
public class MySutRefactoredTest {
private MyCollaborator collaborator;
class MyRefactoredSutSubclassed extends MyRefactoredSut { @Override
protected MyCollaborator createCollaborator() { return collaborator;
}
}
@Test
public void testMyMethod() {
MyRefactoredSut sut = new MyRefactoredSutSubclassed(); collaborator = mock(MyCollaborator.class);
when(collaborator.someMethod()).thenReturn(true); assertTrue(sut.myMethod());
}
}
A new class is introduced - a subclass of the original class (the one we intend to test). An object of this newly introduced class - MyRefactoredSutSubclassed - will be tested.
165
Chapter 7. Points of Controversy
The new class overrides the createCollaborator() method. The new implementation returns a test double of the MyCollaborator class.
There is no need to inject collaborator to sut - this is done by a myMethod() of SUT’s parent class (see Listing 7.18).
This simple technique, as presented in this section, can be really handy. Let us conclude by now listing some of its pros and cons:
•It does not break the clients (the SUT’s API has not been changed) and it lets you write tests at the same time.
•Changes are limited to the SUT class.
•The design of production code is somewhat worse than it was before the refactoring. The newly introduced method is awkward, and poses a potential encapsulation issue. (I must remark that I have never seen any harm being done because of this slight reduction in the SUT’s integrity.)
•The SUT’s myMethod() still creates collaborators (instead of focusing on its business tasks), which means its design has not been improved.
•Some people feel bad about testing a subclass instead of a real SUT. Personally, I would be very happy to test the real thing, but just having a test of a strictly controlled subclass has proved more than adequate for my purposes.
7.6.4. Partial Mocking
The technique demonstrated in this section is very similar to the previously presented subclass and override technique. The difference lies in the implementation, which requires less coding and relies on some Mockito features.
The first step is almost identical to the one previously discussed. You must extract a method which will deal solely with the creation of a new object. Listing 7.20 shows this.
Listing 7.20. Refactored version of the MySut class
public class MyPartialSut {
public boolean myMethod() {
MyCollaborator collaborator = createCollaborator();
// some behaviour worth testing here which uses collaborator
}
// method extracted to facilitate testing MyCollaborator createCollaborator() {
return new MyCollaborator();
}
}
Extracted method.
So far, nothing new here. The difference is only visible when it comes to the tests class. But before we inspect any new code, we need to learn something new about Mockito. Mockito allows us to "spy" on real objects. That means we can decide which methods of a real object are invoked, and which are
166
Chapter 7. Points of Controversy
being intercepted (and stubbed). The Javadocs of the spy() method explains this in the following way:
"Creates a spy of the real object. The spy calls real methods unless they are stubbed."
This feature is deployed in Listing 7.21, which illustrates the use of a partial mocking technique. Additionally, it presents a new static Mockito method - doReturn(). We use
doReturn(collaborator).when(sut).createCollaborator();
instead of
when(collaborator.someMethod()).thenReturn(true);
to avoid execution of the real method (someMethod()), because:
•the real method might throw some exceptions,
•if we are to verify it, then the number of invocations would grow.
Listing 7.21. Partial mocking test of the refactored MySut class
public class MySutPartialTest {
@Test
public void testMyMethod() {
MyPartialSut sut = spy(new MyPartialSut()); MyCollaborator collaborator = mock(MyCollaborator.class);
doReturn(collaborator).when(sut).createCollaborator(); // normal Mockito stubbing/test spying test
}
}
Surprise! The SUT has not been created using the new operator. Instead, another static method of Mockito has been used.
Another unusual situation. We request that our SUT returns some canned values for a given method invocation.
As was mentioned before, when created with the spy() method sut behaves like a normal object of its class, until some of its methods are stubbed. In the case of our example, all the SUT’s methods, except for createCollaborator(), would be executed as if sut had been created with the new keyword. However, this one method - createCollaborator() - is different. When createCollaborator() is executed, it is intercepted by Mockito, which returns some canned values without touching the real sut object at all.
Well, this is confusing! Our SUT is performing two roles here: first of all, it is being tested (which is not shown in the above listing, for reasons of brevity), and secondly, it is being used as a test stub, to provide some canned values.
Partial mocking using the spy() method has its quirks. Make sure you read the Mockito documentation before using it!
As for pros and cons, this technique is very similar to the one discussed previously, with the following differences:
• There is no need to create a subclass. This is done "under the hood" by Mockito.
167