Acknowledgments
Unit Testing, a First Example
public class CustomerTest {
Customer john, steve, pat, david;
String johnName = "John",
steveName = "Steve",
patName = "Pat",
davidName = "David";
Customer[] customers;
@Before
public void setup() {
david = ObjectMother
.customerWithNoRentals(
davidName);
john = ObjectMother
.customerWithOneNewRelease(
johnName);
pat = ObjectMother
.customerWithOneOfEachRentalType(
patName);
steve = ObjectMother
.customerWithOneNewReleaseAndOneRegular(
steveName);
customers =
new Customer[]
{ david, john, steve, pat};
}
@Test
public void getName() {
assertEquals(
davidName, david.getName());
assertEquals(
johnName, john.getName());
assertEquals(
steveName, steve.getName());
assertEquals(
patName, pat.getName());
}
@Test
public void statement() {
for (int i=0; i<customers.length; i++) {
assertEquals(
expStatement(
"Rental record for %s\n" +
"%sAmount owed is %s\n" +
"You earned %s frequent " +
"renter points",
customers[i],
rentalInfo(
"\t", "",
customers[i].getRentals())),
customers[i].statement());
}
}
@Test
public void htmlStatement() {
for (int i=0; i<customers.length; i++) {
assertEquals(
expStatement(
"<h1>Rental record for " +
"<em>%s</em></h1>\n%s" +
"<p>Amount owed is <em>%s</em>" +
"</p>\n<p>You earned <em>%s" +
" frequent renter points</em></p>",
customers[i],
rentalInfo(
"<p>", "</p>",
customers[i].getRentals())),
customers[i].htmlStatement());
}
}
@Test
(expected=IllegalArgumentException.class)
public void invalidTitle() {
ObjectMother
.customerWithNoRentals("Bob")
.addRental(
new Rental(
new Movie("Crazy, Stupid, Love.",
Movie.Type.UNKNOWN),
4));
}
public static String rentalInfo(
String startsWith,
String endsWith,
List<Rental> rentals) {
String result = "";
for (Rental rental : rentals)
result += String.format(
"%s%s\t%s%s\n",
startsWith,
rental.getMovie().getTitle(),
rental.getCharge(),
endsWith);
return result;
}
public static String expStatement(
String formatStr,
Customer customer,
String rentalInfo) {
return String.format(
formatStr,
customer.getName(),
rentalInfo,
customer.getTotalCharge(),
customer.getTotalPoints());
}
}
public class ObjectMother {
public static Customer
customerWithOneOfEachRentalType(
String name) {
Customer result =
customerWithOneNewReleaseAndOneRegular(
name);
result.addRental(
new Rental(
new Movie("Lion King", CHILDREN), 3));
return result;
}
public static Customer
customerWithOneNewReleaseAndOneRegular(
String n) {
Customer result =
customerWithOneNewRelease(n);
result.addRental(
new Rental(
new Movie("Scarface", REGULAR), 3));
return result;
}
public static Customer
customerWithOneNewRelease(
String name) {
Customer result =
customerWithNoRentals(name);
result.addRental(
new Rental(
new Movie(
"Godfather 4", NEW_RELEASE), 3));
return result;
}
public static Customer
customerWithNoRentals(String name) {
return new Customer(name);
}
}
Thoughts on our Tests
The Domain Code
public class Customer {
private String name;
private List<Rental> rentals =
new ArrayList<Rental>();
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public List<Rental> getRentals() {
return rentals;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String statement() {
String result =
"Rental record for " + getName() + "\n";
for (Rental rental : rentals)
result +=
"\t" + rental.getLineItem() + "\n";
result +=
"Amount owed is " + getTotalCharge() +
"\n" + "You earned " +
getTotalPoints() +
" frequent renter points";
return result;
}
public String htmlStatement() {
String result =
"<h1>Rental record for <em>" +
getName() + "</em></h1>\n";
for (Rental rental : rentals)
result += "<p>" + rental.getLineItem() +
"</p>\n";
result +=
"<p>Amount owed is <em>" +
getTotalCharge() + "</em></p>\n" +
"<p>You earned <em>" +
getTotalPoints() +
" frequent renter points</em></p>";
return result;
}
public double getTotalCharge() {
double total = 0;
for (Rental rental : rentals)
total += rental.getCharge();
return total;
}
public int getTotalPoints() {
int total = 0;
for (Rental rental : rentals)
total += rental.getPoints();
return total;
}
}
public class Rental {
Movie movie;
private int daysRented;
public Rental(Movie movie, int daysRented) {
this.movie = movie;
this.daysRented = daysRented;
}
public Movie getMovie() {
return movie;
}
public int getDaysRented() {
return daysRented;
}
public double getCharge() {
return movie.getCharge(daysRented);
}
public int getPoints() {
return movie.getPoints(daysRented);
}
public String getLineItem() {
return
movie.getTitle() + " " + getCharge();
}
}
public class Movie {
public enum Type {
REGULAR, NEW_RELEASE, CHILDREN, UNKNOWN;
}
private String title;
Price price;
public Movie(
String title, Movie.Type priceCode) {
this.title = title;
setPriceCode(priceCode);
}
public String getTitle() {
return title;
}
private void setPriceCode(
Movie.Type priceCode) {
switch (priceCode) {
case CHILDREN:
price = new ChildrensPrice();
break;
case NEW_RELEASE:
price = new NewReleasePrice();
break;
case REGULAR:
price = new RegularPrice();
break;
default:
throw new IllegalArgumentException(
"invalid price code");
}
}
public double getCharge(int daysRented) {
return price.getCharge(daysRented);
}
public int getPoints(int daysRented) {
return price.getPoints(daysRented);
}
}
public abstract class Price {
abstract double getCharge(int daysRented);
int getPoints(int daysRented) {
return 1;
}
}
public class ChildrensPrice extends Price {
@Override
double getCharge(int daysRented) {
double amount = 1.5;
if (daysRented > 3)
amount += (daysRented - 3) * 1.5;
return amount;
}
}
public class RegularPrice extends Price {
@Override
public double getCharge(int daysRented) {
double amount = 2;
if (daysRented > 2)
amount += (daysRented - 2) * 1.5;
return amount;
}
}
public class NewReleasePrice extends Price {
@Override
public double getCharge(int daysRented) {
return daysRented * 3;
}
@Override
int getPoints(int daysRented) {
if (daysRented > 1)
return 2;
return 1;
}
}
Moving Towards Readability
Replace Loop with Individual Tests
public class CustomerTest {
Customer john, steve, pat, david;
String johnName = "John",
steveName = "Steve",
patName = "Pat",
davidName = "David";
Customer[] customers;
@Before
public void setup() {
david = ObjectMother
.customerWithNoRentals(davidName);
john = ObjectMother
.customerWithOneNewRelease(johnName);
pat = ObjectMother
.customerWithOneOfEachRentalType(
patName);
steve = ObjectMother
.customerWithOneNewReleaseAndOneRegular(
steveName);
customers = new Customer[] {
david, john, steve, pat };
}
@Test
public void davidStatement() {
assertEquals(
expStatement(
"Rental record for %s\n%sAmount " +
"owed is %s\nYou earned %s " +
"frequent renter points",
david,
rentalInfo(
"\t", "", david.getRentals())),
david.statement());
}
@Test
public void johnStatement() {
assertEquals(
expStatement(
"Rental record for %s\n%sAmount " +
"owed is %s\nYou earned %s " +
"frequent renter points",
john,
rentalInfo(
"\t", "", john.getRentals())),
john.statement());
}
@Test
public void patStatement() {
assertEquals(
expStatement(
"Rental record for %s\n%sAmount " +
"owed is %s\nYou earned %s " +
"frequent renter points",
pat,
rentalInfo(
"\t", "", pat.getRentals())),
pat.statement());
}
@Test
public void steveStatement() {
assertEquals(
expStatement(
"Rental record for %s\n%s" +
"Amount owed is %s\nYou earned %s " +
"frequent renter points",
steve,
rentalInfo(
"\t", "", steve.getRentals())),
steve.statement());
}
public static String rentalInfo(
String startsWith,
String endsWith,
List<Rental> rentals) {
String result = "";
for (Rental rental : rentals)
result += String.format(
"%s%s\t%s%s\n",
startsWith,
rental.getMovie().getTitle(),
rental.getCharge(),
endsWith);
return result;
}
public static String expStatement(
String formatStr,
Customer customer,
String rentalInfo) {
return String.format(
formatStr,
customer.getName(),
rentalInfo,
customer.getTotalCharge(),
customer.getTotalPoints());
}
}
Expect Literals
public class CustomerTest {
Customer john, steve, pat, david;
String johnName = "John",
steveName = "Steve",
patName = "Pat",
davidName = "David";
Customer[] customers;
@Before
public void setup() {
david = ObjectMother
.customerWithNoRentals(davidName);
john = ObjectMother
.customerWithOneNewRelease(johnName);
pat = ObjectMother
.customerWithOneOfEachRentalType(
patName);
steve = ObjectMother
.customerWithOneNewReleaseAndOneRegular(
steveName);
customers = new Customer[] {
david, john, steve, pat };
}
@Test
public void davidStatement() {
assertEquals(
"Rental record for David\nAmount " +
"owed is 0.0\n" +
"You earned 0 frequent renter points",
david.statement());
}
@Test
public void johnStatement() {
assertEquals(
"Rental record for John\n\t" +
"Godfather 4\t9.0\n" +
"Amount owed is 9.0\n" +
"You earned 2 frequent renter points",
john.statement());
}
@Test
public void patStatement() {
assertEquals(
"Rental record for Pat\n\t" +
"Godfather 4\t9.0\n" +
"\tScarface\t3.5\n" +
"\tLion King\t1.5\n" +
"Amount owed is 14.0\n" +
"You earned 4 frequent renter points",
pat.statement());
}
@Test
public void steveStatement() {
assertEquals(
"Rental record for Steve\n\t" +
"Godfather 4\t9.0\n" +
"\tScarface\t3.5\n" +
"Amount owed is 12.5\n" +
"You earned 3 frequent renter points",
steve.statement());
}
}
Inline Setup
public class CustomerTest {
@Test
public void noRentalsStatement() {
assertEquals(
"Rental record for David\nAmount " +
"owed is 0.0\n" +
"You earned 0 frequent renter points",
ObjectMother
.customerWithNoRentals(
"David").statement());
}
@Test
public void oneNewReleaseStatement() {
assertEquals(
"Rental record for John\n\t" +
"Godfather 4 9.0\n" +
"Amount owed is 9.0\n" +
"You earned 2 frequent renter points",
ObjectMother
.customerWithOneNewRelease(
"John").statement());
}
@Test
public void allRentalTypesStatement() {
assertEquals(
"Rental record for Pat\n\t" +
"Godfather 4 9.0\n" +
"\tScarface 3.5\n\tLion King 1.5\n" +
"Amount owed is 14.0\n" +
"You earned 4 frequent renter points",
ObjectMother
.customerWithOneOfEachRentalType(
"Pat").statement());
}
@Test
public void
newReleaseAndRegularStatement() {
assertEquals(
"Rental record for Steve\n\t" +
"Godfather 4 9.0\n" +
"\tScarface 3.5\n" +
"Amount owed is 12.5\n" +
"You earned 3 frequent renter points",
ObjectMother
.customerWithOneNewReleaseAndOneRegular(
"Steve").statement());
}
}
Replace ObjectMother with DataBuilder
public class a {
public static CustomerBuilder customer =
new CustomerBuilder();
public static RentalBuilder rental =
new RentalBuilder();
public static MovieBuilder movie =
new MovieBuilder();
public static class CustomerBuilder {
Rental[] rentals;
String name;
CustomerBuilder() {
this("Jim", new Rental[0]);
}
CustomerBuilder(
String name, Rental[] rentals) {
this.name = name;
this.rentals = rentals;
}
public CustomerBuilder w(
RentalBuilder... builders) {
Rental[] rentals =
new Rental[builders.length];
for (int i=0; i<builders.length; i++) {
rentals[i] = builders[i].build();
}
return
new CustomerBuilder(name, rentals);
}
public CustomerBuilder w(String name) {
return
new CustomerBuilder(name, rentals);
}
public Customer build() {
Customer result = new Customer(name);
for (Rental rental : rentals) {
result.addRental(rental);
}
return result;
}
}
public static class RentalBuilder {
final Movie movie;
final int days;
RentalBuilder() {
this(new MovieBuilder().build(), 3);
}
RentalBuilder(Movie movie, int days) {
this.movie = movie;
this.days = days;
}
public RentalBuilder w(
MovieBuilder movie) {
return
new RentalBuilder(
movie.build(), days);
}
public Rental build() {
return new Rental(movie, days);
}
}
public static class MovieBuilder {
final String name;
final Movie.Type type;
MovieBuilder() {
this("Godfather 4",
Movie.Type.NEW_RELEASE);
}
MovieBuilder(
String name, Movie.Type type) {
this.name = name;
this.type = type;
}
public MovieBuilder w(Movie.Type type) {
return new MovieBuilder(name, type);
}
public MovieBuilder w(String name) {
return new MovieBuilder(name, type);
}
public Movie build() {
return new Movie(name, type);
}
}
}
public class CustomerTest {
@Test
public void noRentalsStatement() {
assertEquals(
"Rental record for David\nAmount " +
"owed is 0.0\nYou earned 0 frequent " +
"renter points",
a.customer.w(
"David").build().statement());
}
@Test
public void oneNewReleaseStatement() {
assertEquals(
"Rental record for John\n\t" +
"Godfather 4 9.0\nAmount owed is " +
"9.0\nYou earned 2 frequent renter " +
"points",
a.customer.w("John").w(
a.rental.w(
a.movie.w(NEW_RELEASE))).build()
.statement());
}
@Test
public void allRentalTypesStatement() {
assertEquals(
"Rental record for Pat\n\t" +
"Godfather 4 9.0\n\tScarface 3.5\n" +
"\tLion King 1.5\nAmount owed is " +
"14.0\nYou earned 4 frequent renter " +
"points",
a.customer.w("Pat").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(a.movie.w("Scarface").w(
REGULAR)),
a.rental.w(a.movie.w("Lion King").w(
CHILDREN))).build()
.statement());
}
@Test
public void
newReleaseAndRegularStatement() {
assertEquals(
"Rental record for Steve\n\t" +
"Godfather 4 9.0\n\tScarface 3.5\n" +
"Amount owed is 12.5\nYou earned 3 " +
"frequent renter points",
a.customer.w("Steve").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(
a.movie.w(
"Scarface").w(REGULAR))).build()
.statement());
}
}
Comparing the Results
public class CustomerTest {
Customer john, steve, pat, david;
String johnName = "John",
steveName = "Steve",
patName = "Pat",
davidName = "David";
Customer[] customers;
@Before
public void setup() {
david = ObjectMother
.customerWithNoRentals(
davidName);
john = ObjectMother
.customerWithOneNewRelease(
johnName);
pat = ObjectMother
.customerWithOneOfEachRentalType(
patName);
steve = ObjectMother
.customerWithOneNewReleaseAndOneRegular(
steveName);
customers =
new Customer[]
{ david, john, steve, pat};
}
@Test
public void getName() {
assertEquals(
davidName, david.getName());
assertEquals(
johnName, john.getName());
assertEquals(
steveName, steve.getName());
assertEquals(
patName, pat.getName());
}
@Test
public void statement() {
for (int i=0; i<customers.length; i++) {
assertEquals(
expStatement(
"Rental record for %s\n" +
"%sAmount owed is %s\n" +
"You earned %s frequent " +
"renter points",
customers[i],
rentalInfo(
"\t", "",
customers[i].getRentals())),
customers[i].statement());
}
}
@Test
public void htmlStatement() {
for (int i=0; i<customers.length; i++) {
assertEquals(
expStatement(
"<h1>Rental record for " +
"<em>%s</em></h1>\n%s" +
"<p>Amount owed is <em>%s</em>" +
"</p>\n<p>You earned <em>%s" +
" frequent renter points</em></p>",
customers[i],
rentalInfo(
"<p>", "</p>",
customers[i].getRentals())),
customers[i].htmlStatement());
}
}
@Test
(expected=IllegalArgumentException.class)
public void invalidTitle() {
ObjectMother
.customerWithNoRentals("Bob")
.addRental(
new Rental(
new Movie("Crazy, Stupid, Love.",
Movie.Type.UNKNOWN),
4));
}
public static String rentalInfo(
String startsWith,
String endsWith,
List<Rental> rentals) {
String result = "";
for (Rental rental : rentals)
result += String.format(
"%s%s\t%s%s\n",
startsWith,
rental.getMovie().getTitle(),
rental.getCharge(),
endsWith);
return result;
}
public static String expStatement(
String formatStr,
Customer customer,
String rentalInfo) {
return String.format(
formatStr,
customer.getName(),
rentalInfo,
customer.getTotalCharge(),
customer.getTotalPoints());
}
}
public class CustomerTest {
@Test
public void getName() {
assertEquals(
"John",
a.customer.w(
"John").build().getName());
}
@Test
public void noRentalsStatement() {
assertEquals(
"Rental record for David\nAmount " +
"owed is 0.0\nYou earned 0 frequent " +
"renter points",
a.customer.w(
"David").build().statement());
}
@Test
public void oneNewReleaseStatement() {
assertEquals(
"Rental record for John\n" +
"\tGodfather 4 9.0\n" +
"Amount owed is 9.0\n" +
"You earned 2 frequent renter points",
a.customer.w("John").w(
a.rental.w(
a.movie.w(
NEW_RELEASE))).build()
.statement());
}
@Test
public void allRentalTypesStatement() {
assertEquals(
"Rental record for Pat\n" +
"\tGodfather 4 9.0\n" +
"\tScarface 3.5\n" +
"\tLion King 1.5\n" +
"Amount owed is 14.0\n" +
"You earned 4 frequent renter points",
a.customer.w("Pat").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(
a.movie.w("Scarface").w(REGULAR)),
a.rental.w(
a.movie.w("Lion King").w(
CHILDREN))).build().statement());
}
@Test
public void
newReleaseAndRegularStatement() {
assertEquals(
"Rental record for Steve\n" +
"\tGodfather 4 9.0\n" +
"\tScarface 3.5\n" +
"Amount owed is 12.5\n" +
"You earned 3 frequent renter points",
a.customer.w("Steve").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(
a.movie.w("Scarface").w(
REGULAR))).build().statement());
}
@Test
public void noRentalsHtmlStatement() {
assertEquals(
"<h1>Rental record for <em>David" +
"</em></h1>\n<p>Amount owed is <em>" +
"0.0</em></p>\n<p>You earned <em>0 " +
"frequent renter points</em></p>",
a.customer.w(
"David").build().htmlStatement());
}
@Test
public void oneNewReleaseHtmlStatement() {
assertEquals(
"<h1>Rental record for <em>John</em>" +
"</h1>\n<p>Godfather 4 9.0</p>\n" +
"<p>Amount owed is <em>9.0</em></p>" +
"\n<p>You earned <em>2 frequent " +
"renter points</em></p>",
a.customer.w("John").w(
a.rental.w(
a.movie.w(
NEW_RELEASE))).build()
.htmlStatement());
}
@Test
public void allRentalTypesHtmlStatement() {
assertEquals(
"<h1>Rental record for <em>Pat</em>" +
"</h1>\n<p>Godfather 4 9.0</p>\n" +
"<p>Scarface 3.5</p>\n<p>Lion King" +
" 1.5</p>\n<p>Amount owed is <em>" +
"14.0</em></p>\n<p>You earned <em>" +
"4 frequent renter points</em></p>",
a.customer.w("Pat").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(
a.movie.w("Scarface").w(REGULAR)),
a.rental.w(
a.movie.w("Lion King").w(
CHILDREN))).build()
.htmlStatement());
}
@Test
public void
newReleaseAndRegularHtmlStatement() {
assertEquals(
"<h1>Rental record for <em>Steve" +
"</em></h1>\n<p>Godfather 4 9.0</p>" +
"\n<p>Scarface 3.5</p>\n<p>Amount " +
"owed is <em>12.5</em></p>\n<p>" +
"You earned <em>3 frequent renter " +
"points</em></p>",
a.customer.w("Steve").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(
a.movie.w("Scarface").w(
REGULAR))).build()
.htmlStatement());
}
@Test
(expected=IllegalArgumentException.class)
public void invalidTitle() {
a.customer.w(
a.rental.w(
a.movie.w(UNKNOWN))).build();
}
}
Final Thoughts on our Tests
Motivators
Validate the System
Common motivators that would be a subset of Validate the System
Code Coverage
Enable Refactoring
Document the Behavior of the System
Your Manager Told You To
Test Driven Development
Common motivators that would be a subset of TDD
Customer Acceptance
Ping Pong Pair-Programming
What Motivates You (or Your Team)
Types of Tests
Strongly Recommended Reference Material
State Verification
public class RentalTest {
@Test
public void rentalIsStartedIfInStore() {
Movie movie = a.movie.build();
Rental rental =
a.rental.w(movie).build();
Store store = a.store.w(movie).build();
rental.start(store);
assertTrue(rental.isStarted());
assertEquals(
0, store.getAvailability(movie));
}
@Test
public void
rentalDoesNotStartIfNotAvailable() {
Movie movie = a.movie.build();
Rental rental = a.rental.build();
Store store = a.store.build();
rental.start(store);
assertFalse(rental.isStarted());
assertEquals(
0, store.getAvailability(movie));
}
}
Behavior Verification
public class RentalTest {
@Test
public void rentalIsStartedIfInStore() {
Movie movie = a.movie.build();
Rental rental =
a.rental.w(movie).build();
Store store = mock(Store.class);
when(store.getAvailability(movie))
.thenReturn(1);
rental.start(store);
assertTrue(rental.isStarted());
verify(store).remove(movie);
}
@Test
public void
rentalDoesNotStartIfNotAvailable() {
Rental rental = a.rental.build();
Store store = mock(Store.class);
rental.start(store);
assertFalse(rental.isStarted());
verify(
store, never()).remove(
any(Movie.class));
}
}
Picking a Side
Unit Test
Solitary Unit Test
Sociable Unit Test
Continuing with Examples From Chapter 1
public class CustomerTest {
@Test
public void getName() {
assertEquals(
"John",
a.customer.w(
"John").build().getName());
}
@Test
public void noRentalsStatement() {
assertEquals(
"Rental record for David\nAmount" +
" owed is 0.0\n" +
"You earned 0 frequent renter points",
a.customer.w(
"David").build().statement());
}
@Test
public void oneNewReleaseStatement() {
assertEquals(
"Rental record for John\n" +
"\tGodfather 4 9.0\n" +
"Amount owed is 9.0\n" +
"You earned 2 frequent renter points",
a.customer.w("John").w(
a.rental.w(
a.movie.w(
NEW_RELEASE))).build()
.statement());
}
@Test
public void allRentalTypesStatement() {
assertEquals(
"Rental record for Pat\n" +
"\tGodfather 4 9.0\n" +
"\tScarface 3.5\n" +
"\tLion King 1.5\n" +
"Amount owed is 14.0\n" +
"You earned 4 frequent renter points",
a.customer.w("Pat").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(
a.movie.w("Scarface").w(REGULAR)),
a.rental.w(
a.movie.w(
"Lion King").w(
CHILDREN))).build()
.statement());
}
@Test
public void
newReleaseAndRegularStatement() {
assertEquals(
"Rental record for Steve\n" +
"\tGodfather 4 9.0\n" +
"\tScarface 3.5\n" +
"Amount owed is 12.5\n" +
"You earned 3 frequent renter points",
a.customer.w("Steve").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(
a.movie.w("Scarface").w(
REGULAR))).build()
.statement());
}
@Test
public void noRentalsHtmlStatement() {
assertEquals(
"<h1>Rental record for <em>David" +
"</em></h1>\n<p>Amount owed is " +
"<em>0.0</em></p>\n<p>" +
"You earned <em>0 frequent renter " +
"points</em></p>",
a.customer.w(
"David").build().htmlStatement());
}
@Test
public void oneNewReleaseHtmlStatement() {
assertEquals(
"<h1>Rental record for <em>John</em>" +
"</h1>\n<p>Godfather 4 9.0</p>\n" +
"<p>Amount owed is <em>9.0</em></p>" +
"\n<p>You earned <em>2 frequent " +
"renter points</em></p>",
a.customer.w("John").w(
a.rental.w(
a.movie.w(NEW_RELEASE))).build()
.htmlStatement());
}
@Test
public void allRentalTypesHtmlStatement() {
assertEquals(
"<h1>Rental record for <em>Pat</em>" +
"</h1>\n<p>Godfather 4 9.0</p>\n<p>" +
"Scarface 3.5</p>\n<p>Lion King 1.5" +
"</p>\n<p>Amount owed is <em>14.0" +
"</em></p>\n<p>You earned <em>4 " +
"frequent renter points</em></p>",
a.customer.w("Pat").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(a.movie.w("Scarface").w(
REGULAR)),
a.rental.w(a.movie.w("Lion King").w(
CHILDREN))).build()
.htmlStatement());
}
@Test
public void
newReleaseAndRegularHtmlStatement() {
assertEquals(
"<h1>Rental record for <em>Steve" +
"</em></h1>\n<p>Godfather 4 9.0</p>" +
"\n<p>Scarface 3.5</p>\n<p>Amount " +
"owed is <em>12.5</em></p>\n<p>You " +
"earned <em>3 frequent renter points" +
"</em></p>",
a.customer.w("Steve").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(a.movie.w("Scarface").w(
REGULAR))).build()
.htmlStatement());
}
@Test
(expected=IllegalArgumentException.class)
public void invalidTitle() {
a.customer.w(
a.rental.w(
a.movie.w(UNKNOWN))).build();
}
}
public class MovieTest {
@Test
(expected=IllegalArgumentException.class)
public void invalidTitle() {
a.movie.w(UNKNOWN).build();
}
}
public class CustomerTest {
@Test
public void noRentalsStatement() {
assertEquals(
"Rental record for Jim\nAmount owed" +
" is 0.0\n" +
"You earned 0 frequent renter points",
a.customer.build().statement());
}
@Test
public void oneRentalStatement() {
assertEquals(
"Rental record for Jim\n\tnull\n" +
"Amount owed is 0.0\n" +
"You earned 0 frequent renter points",
a.customer.w(
mock(Rental.class)).build()
.statement());
}
@Test
public void twoRentalsStatement() {
assertEquals(
"Rental record for Jim\n\t" +
"null\n\tnull\n" +
"Amount owed is 0.0\n" +
"You earned 0 frequent renter points",
a.customer.w(
mock(Rental.class),
mock(Rental.class)).build()
.statement());
}
}
public class CustomerTest {
@Test
public void noRentalsCharge() {
assertEquals(
0.0,
a.customer.build().getTotalCharge(),
0);
}
@Test
public void twoRentalsCharge() {
Rental rental = mock(Rental.class);
when(rental.getCharge()).thenReturn(2.0);
assertEquals(
4.0,
a.customer.w(
rental,
rental).build().getTotalCharge(),
0);
}
@Test
public void threeRentalsCharge() {
Rental rental = mock(Rental.class);
when(rental.getCharge()).thenReturn(2.0);
assertEquals(
6.0,
a.customer.w(
rental,
rental,
rental).build().getTotalCharge(),
0);
}
@Test
public void noRentalsPoints() {
assertEquals(
0,
a.customer.build().getTotalPoints());
}
@Test
public void twoRentalsPoints() {
Rental rental = mock(Rental.class);
when(rental.getPoints()).thenReturn(2);
assertEquals(
4,
a.customer.w(
rental,
rental).build().getTotalPoints());
}
@Test
public void threeRentalsPoints() {
Rental rental = mock(Rental.class);
when(rental.getPoints()).thenReturn(2);
assertEquals(
6,
a.customer.w(
rental,
rental,
rental).build().getTotalPoints());
}
}
public class MovieTest {
@Test
public void getChargeForChildrens() {
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(1),
0);
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(2),
0);
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(3),
0);
assertEquals(
3.0,
a.movie.w(
CHILDREN).build().getCharge(4),
0);
assertEquals(
4.5,
a.movie.w(
CHILDREN).build().getCharge(5),
0);
}
@Test
public void getChargeForNewRelease() {
assertEquals(
3.0,
a.movie.w(
NEW_RELEASE).build().getCharge(1),
0);
assertEquals(
6.0,
a.movie.w(
NEW_RELEASE).build().getCharge(2),
0);
assertEquals(
9.0,
a.movie.w(
NEW_RELEASE).build().getCharge(3),
0);
}
@Test
public void getChargeForRegular() {
assertEquals(
2.0,
a.movie.w(
REGULAR).build().getCharge(1),
0);
assertEquals(
2.0,
a.movie.w(
REGULAR).build().getCharge(2),
0);
assertEquals(
3.5,
a.movie.w(
REGULAR).build().getCharge(3),
0);
assertEquals(
5.0,
a.movie.w(
REGULAR).build().getCharge(4),
0);
}
@Test
public void getPointsForChildrens() {
assertEquals(
1,
a.movie.w(
CHILDREN).build().getPoints(1));
assertEquals(
1,
a.movie.w(
CHILDREN).build().getPoints(2));
}
@Test
public void getPointsForNewRelease() {
assertEquals(
1,
a.movie.w(
NEW_RELEASE).build().getPoints(1));
assertEquals(
2,
a.movie.w(
NEW_RELEASE).build().getPoints(2));
assertEquals(
2,
a.movie.w(
NEW_RELEASE).build().getPoints(3));
}
@Test
public void getPointsForRegular() {
assertEquals(
1,
a.movie.w(
REGULAR).build().getPoints(1));
assertEquals(
1,
a.movie.w(
REGULAR).build().getPoints(2));
}
}
public class CustomerTest {
@Test
public void allRentalTypesStatement() {
assertEquals(
"Rental record for Pat\n" +
"\tGodfather 4 9.0\n" +
"\tScarface 3.5\n" +
"\tLion King 1.5\n" +
"Amount owed is 14.0\n" +
"You earned 4 frequent renter points",
a.customer.w("Pat").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(a.movie.w("Scarface").w(
REGULAR)),
a.rental.w(a.movie.w("Lion King").w(
CHILDREN)))
.build().statement());
}
@Test
public void allRentalTypesHtmlStatement() {
assertEquals(
"<h1>Rental record for " +
"<em>Pat</em></h1>\n" +
"<p>Godfather 4 9.0</p>\n" +
"<p>Scarface 3.5</p>\n" +
"<p>Lion King 1.5</p>\n" +
"<p>Amount owed is " +
"<em>14.0</em></p>\n<p>" +
"You earned <em>4 frequent " +
"renter points</em></p>",
a.customer.w("Pat").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(a.movie.w("Scarface").w(
REGULAR)),
a.rental.w(a.movie.w("Lion King").w(
CHILDREN)))
.build().htmlStatement());
}
}
Final Thoughts, Again
The Failure
public class CustomerTest {
@Test
public void getName() {
assertEquals(
"John",
a.customer.w(
"John").build().getName());
}
@Test
public void noRentalsStatement() {
assertEquals(
"Rental record for Jim\nAmount owed" +
" is 0.0\nYou earned 0 frequent " +
"renter points",
a.customer.build().statement());
}
@Test
public void oneRentalStatement() {
assertEquals(
"Rental record for Jim\n\tnull\n" +
"Amount owed is 0.0\n" +
"You earned 0 frequent renter points",
a.customer.w(
mock(Rental.class)).build()
.statement());
}
@Test
public void twoRentalsStatement() {
assertEquals(
"Rental record for Jim\n\tnull\n" +
"\tnull\nAmount owed is 0.0\n" +
"You earned 0 frequent renter points",
a.customer.w(
mock(Rental.class),
mock(Rental.class)).build()
.statement());
}
@Test
public void noRentalsHtmlStatement() {
assertEquals(
"<h1>Rental record for <em>Jim</em>" +
"</h1>\n<p>Amount owed is <em>0.0" +
"</em></p>\n<p>You earned <em>0 " +
"frequent renter points</em></p>",
a.customer.build().htmlStatement());
}
@Test
public void oneRentalHtmlStatement() {
Rental rental = mock(Rental.class);
assertEquals(
"<h1>Rental record for <em>Jim</em>" +
"</h1>\n<p>null</p>\n<p>Amount owed " +
"is <em>0.0</em></p>\n<p>You earned " +
"<em>0 frequent renter points</em>" +
"</p>",
a.customer.w(
mock(Rental.class)).build()
.htmlStatement());
}
@Test
public void twoRentalsHtmlStatement() {
assertEquals(
"<h1>Rental record for <em>Jim</em>" +
"</h1>\n<p>null</p>\n<p>null</p>\n" +
"<p>Amount owed is <em>0.0</em></p>" +
"\n<p>You earned <em>0 frequent" +
" renter points</em></p>",
a.customer.w(
mock(Rental.class),
mock(Rental.class)).build()
.htmlStatement());
}
@Test
public void noRentalsCharge() {
assertEquals(
0.0,
a.customer.build().getTotalCharge(),
0);
}
@Test
public void twoRentalsCharge() {
Rental rental = mock(Rental.class);
when(rental.getCharge()).thenReturn(2.0);
assertEquals(
6.0,
a.customer.w(
rental,
rental).build().getTotalCharge(),
0);
}
@Test
public void threeRentalsCharge() {
Rental rental = mock(Rental.class);
when(rental.getCharge()).thenReturn(2.0);
assertEquals(
6.0,
a.customer.w(
rental,
rental,
rental).build().getTotalCharge(),
0);
}
@Test
public void noRentalsPoints() {
assertEquals(
0,
a.customer.build().getTotalPoints());
}
@Test
public void twoRentalsPoints() {
Rental rental = mock(Rental.class);
when(rental.getPoints()).thenReturn(2);
assertEquals(
4,
a.customer.w(
rental,
rental).build().getTotalPoints());
}
@Test
public void threeRentalsPoints() {
Rental rental = mock(Rental.class);
when(rental.getPoints()).thenReturn(2);
assertEquals(
6,
a.customer.w(
rental,
rental,
rental).build().getTotalPoints());
}
}
public class CustomerTest {
@Test
public void allRentalTypesStatement() {
assertEquals(
"Rental record for Pat\n" +
"\tGodfather 4 9.0\n" +
"\tScarface 3.5\n" +
"\tLion King 1.5\n" +
"Amount owed is 14.0\n" +
"You earned 4 frequent renter points",
a.customer.w("Pat").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(a.movie.w("Scarface").w(
REGULAR)),
a.rental.w(a.movie.w("Lion King").w(
CHILDREN)))
.build().statement());
}
@Test
public void allRentalTypesHtmlStatement() {
assertEquals(
"<h1>Rental record for <em>Pat" +
"</em></h1>\n" +
"<p>Godfather 4 9.0</p>\n" +
"<p>Scarface 3.5</p>\n" +
"<p>Lion King 1.5</p>\n" +
"<p>Amount owed is " +
"<em>14.0</em></p>\n<p>" +
"You earned <em>4 " +
"frequent renter points</em></p>",
a.customer.w("Pat").w(
a.rental.w(a.movie.w(NEW_RELEASE)),
a.rental.w(a.movie.w("Scarface").w(
REGULAR)),
a.rental.w(a.movie.w("Lion King").w(
CHILDREN)))
.build().htmlStatement());
}
}
public class Customer {
private String name;
private List<Rental> rentals
= new ArrayList<Rental>();
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String statement() {
String result =
"Rental record for " +
getName() + "\n";
for (Rental rental : rentals)
result +=
"\t" + rental.getLineItem() + "\n";
result += "Amount owed is " +
getTotalCharge() + "\n" +
"You earned " + getTotalPoints() +
" frequent renter points";
return result;
}
public String htmlStatement() {
String result =
"<h1>Rental record for <em>" +
getName() + "</em></h1>\n";
for (Rental rental : rentals)
result +=
"<p>" + rental.getLineItem() +
"</p>\n";
result +=
"<p>Amount owed is <em>" +
getTotalCharge() + "</em></p>\n" +
"<p>You earned <em>" +
getTotalPoints() +
" frequent renter points</em></p>";
return result;
}
public double getTotalCharge() {
double total = 0;
for (Rental rental : rentals)
total += rental.getCharge();
return total;
}
public int getTotalPoints() {
int total = 0;
for (Rental rental : rentals)
total += rental.getPoints();
return total;
}
}
public class MovieTest {
@Test
public void getChargeForChildrens() {
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(1),
0);
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(2),
0);
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(3),
0);
assertEquals(
3.0,
a.movie.w(
CHILDREN).build().getCharge(4),
0);
assertEquals(
4.5,
a.movie.w(
CHILDREN).build().getCharge(5),
0);
}
@Test
public void getChargeForNewRelease() {
assertEquals(
3.0,
a.movie.w(
NEW_RELEASE).build().getCharge(1),
0);
assertEquals(
6.0,
a.movie.w(
NEW_RELEASE).build().getCharge(2),
0);
assertEquals(
9.0,
a.movie.w(
NEW_RELEASE).build().getCharge(3),
0);
}
@Test
public void getChargeForRegular() {
assertEquals(
2.0,
a.movie.w(
REGULAR).build().getCharge(1),
0);
assertEquals(
2.0,
a.movie.w(
REGULAR).build().getCharge(2),
0);
assertEquals(
3.5,
a.movie.w(
REGULAR).build().getCharge(3),
0);
assertEquals(
5.0,
a.movie.w(
REGULAR).build().getCharge(4),
0);
}
@Test
public void getPointsForChildrens() {
assertEquals(
1,
a.movie.w(
CHILDREN).build().getPoints(1));
assertEquals(
1,
a.movie.w(
CHILDREN).build().getPoints(2));
}
@Test
public void getPointsForNewRelease() {
assertEquals(
1,
a.movie.w(
NEW_RELEASE).build().getPoints(1));
assertEquals(
2,
a.movie.w(
NEW_RELEASE).build().getPoints(2));
assertEquals(
2,
a.movie.w(
NEW_RELEASE).build().getPoints(3));
}
@Test
public void getPointsForRegular() {
assertEquals(
1,
a.movie.w(
REGULAR).build().getPoints(1));
assertEquals(
1,
a.movie.w(
REGULAR).build().getPoints(2));
}
@Test
(expected=IllegalArgumentException.class)
public void invalidTitle() {
a.movie.w(UNKNOWN).build();
}
}
public class Movie {
public enum Type {
REGULAR, NEW_RELEASE, CHILDREN, UNKNOWN;
}
private String title;
Price price;
public Movie(
String title, Movie.Type priceCode) {
this.title = title;
setPriceCode(priceCode);
}
public String getTitle() {
return title;
}
private void setPriceCode(
Movie.Type priceCode) {
switch (priceCode) {
case CHILDREN:
price = new ChildrensPrice();
break;
case NEW_RELEASE:
price = new NewReleasePrice();
break;
case REGULAR:
price = new RegularPrice();
break;
default:
throw new IllegalArgumentException(
"invalid price code");
}
}
public double getCharge(int daysRented) {
return price.getCharge(daysRented);
}
public int getPoints(int daysRented) {
return price.getPoints(daysRented);
}
}
public class RentalTest {
@Test
public void
isStartedIfInStoreStateBased() {
Movie movie = a.movie.build();
Rental rental =
a.rental.w(movie).build();
Store store = a.store.w(movie).build();
rental.start(store);
assertTrue(rental.isStarted());
assertEquals(
0, store.getAvailability(movie));
}
@Test
public void
doesNotStartIfNotAvailableStateBased() {
Movie movie = a.movie.build();
Rental rental = a.rental.build();
Store store = a.store.build();
rental.start(store);
assertFalse(rental.isStarted());
assertEquals(
0, store.getAvailability(movie));
}
@Test
public void
isStartedIfInStoreInteractionBased() {
Movie movie = a.movie.build();
Rental rental =
a.rental.w(movie).build();
Store store = mock(Store.class);
when(store.getAvailability(movie))
.thenReturn(1);
rental.start(store);
assertTrue(rental.isStarted());
verify(store).remove(movie);
}
@Test
public void
notStartedIfUnavailableInteractionBased() {
Rental rental = a.rental.build();
Store store = mock(Store.class);
rental.start(store);
assertFalse(rental.isStarted());
verify(
store, never()).remove(
any(Movie.class));
}
}
public class Rental {
Movie movie;
private int daysRented;
private boolean started;
public Rental(
Movie movie, int daysRented) {
this.movie = movie;
this.daysRented = daysRented;
}
public Movie getMovie() {
return movie;
}
public int getDaysRented() {
return daysRented;
}
public double getCharge() {
return movie.getCharge(daysRented);
}
public int getPoints() {
return movie.getPoints(daysRented);
}
public String getLineItem() {
return
movie.getTitle() + " " + getCharge();
}
public boolean isStarted() {
return started;
}
public void start(Store store) {
if (store.getAvailability(movie) > 0) {
store.remove(movie);
this.started = true;
}
}
}
public class Store {
private Map<Movie, Integer> movies;
public Store(Map<Movie, Integer> movies) {
this.movies = movies;
}
public int getAvailability(Movie movie) {
if (null == movies.get(movie))
return 0;
return movies.get(movie);
}
public boolean getAvailability(
Movie movie, int quantity) {
if (null == movies.get(movie))
return false;
return movies.get(movie) >= quantity;
}
public void remove(Movie movie) {
if (null == movies.get(movie))
return;
Integer count = movies.get(movie);
movies.put(movie, --count);
}
}
Improving Assertions
One Assertion Per Test
public class MovieTest {
@Test
public void getChargeForChildrens() {
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(1),
0);
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(2),
0);
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(3),
0);
assertEquals(
3.0,
a.movie.w(
CHILDREN).build().getCharge(4),
0);
assertEquals(
4.5,
a.movie.w(
CHILDREN).build().getCharge(5),
0);
}
}
Failure
public class ChildrensPrice extends Price {
@Override
public double getCharge(int daysRented) {
double amount = 1.5;
if (daysRented > 2) // *was 3*
amount += (daysRented - 2) * 1.5;
return amount;
}
}
Test Naming
The Solution
public class MovieTest {
@Test
public void getChargeForChildrens1Day() {
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(1),
0);
}
@Test
public void getChargeForChildrens2Day() {
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(2),
0);
}
@Test
public void getChargeForChildrens3Day() {
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(3),
0);
}
@Test
public void getChargeForChildrens4Day() {
assertEquals(
3.0,
a.movie.w(
CHILDREN).build().getCharge(4),
0);
}
@Test
public void getChargeForChildrens5Day() {
assertEquals(
4.5,
a.movie.w(
CHILDREN).build().getCharge(5),
0);
}
}
Applying One Assertion Per Test To Behavior Verification Tests
public class RentalTest {
@Test
public void rentalIsStartedIfInStore() {
Movie movie = mock(Movie.class);
Rental rental =
a.rental.w(movie).build();
Store store = mock(Store.class);
when(store.getAvailability(movie))
.thenReturn(1);
rental.start(store);
assertTrue(rental.isStarted());
verify(store).remove(movie);
}
}
public class RentalTest {
@Test
public void rentalIsStartedIfInStore() {
Movie movie = mock(Movie.class);
Rental rental =
a.rental.w(movie).build();
Store store = mock(Store.class);
when(store.getAvailability(movie, 1))
.thenReturn(true);
rental.start(store);
assertTrue(rental.isStarted());
verify(store).remove(movie);
}
}
public class RentalTest {
@Test
public void rentalIsStartedIfInStore() {
Rental rental = a.rental.build();
Store store = mock(Store.class);
when(store
.getAvailability(any(Movie.class)))
.thenReturn(1);
rental.start(store);
assertTrue(rental.isStarted());
}
}
public class StoreTest {
@Test
public void storeWithNoAvailability() {
Store store = a.store.build();
assertEquals(
0,
store.getAvailability(
mock(Movie.class)));
}
@Test
public void storeWithAvailability() {
Movie movie = mock(Movie.class);
Store store =
a.store.w(movie, movie).build();
assertEquals(
2, store.getAvailability(movie));
}
@Test
public void
storeWithRemovedAvailability() {
Movie movie = mock(Movie.class);
Store store =
a.store.w(movie, movie).build();
store.remove(movie);
assertEquals(
1, store.getAvailability(movie));
}
}
public class RentalTest {
@Test
public void
storeAvailabilityIsModifiedOnRental() {
Movie movie = a.movie.build();
Rental rental =
a.rental.w(movie).build();
Store store =
a.store.w(movie, movie).build();
rental.start(store);
a.rental.build().start(store);
assertEquals(
1, store.getAvailability(movie));
}
}
Thoughts On The Result
Implementation Overspecification
public class Customer {
private String name;
private List<Rental> rentals =
new ArrayList<Rental>();
public Customer(String name) {
this.name = name;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String recentRentals() {
String result = "Recent rentals:";
for (int i=0;
i < rentals.size() && i < 3;
i++) {
result += "\n" +
rentals.get(i).getMovie(
true).getTitle(
"%s starring %s %s", 2);
}
return result;
}
}
public class CustomerTest {
@Test
public void recentRentalsWith2Rentals() {
Movie godfather = mock(Movie.class);
when(godfather
.getTitle("%s starring %s %s", 2))
.thenReturn("Godfather 4");
Rental godfatherRental =
mock(Rental.class);
when(godfatherRental.getMovie(true))
.thenReturn(godfather);
Movie lionKing = mock(Movie.class);
when(lionKing
.getTitle("%s starring %s %s", 2))
.thenReturn("Lion King");
Rental lionKingRental =
mock(Rental.class);
when(lionKingRental.getMovie(true))
.thenReturn(lionKing);
assertEquals(
"Recent rentals:\nGodfather 4\n" +
"Lion King",
a.customer.w(
godfatherRental, lionKingRental)
.build().recentRentals());
}
@Test
public void recentRentalsWith3Rentals() {
// same structure as above, with
// 8 more lines of mocking code,
// 25% longer expected value, and
// 2 lines of adding rentals to customer
}
@Test
public void recentRentalsWith4Rentals() {
// same structure as above, with
// 16 more lines of mocking code,
// 25% longer expected value, and
// 2 lines of adding rentals to customer
}
}
Flexible Argument Matchers
public class CustomerTest {
@Test
public void recentRentalsWith2Rentals() {
Movie godfather = mock(Movie.class);
when(
godfather.getTitle(
anyString(), anyInt()))
.thenReturn("Godfather 4");
Rental godfatherRental =
mock(Rental.class);
when(
godfatherRental.getMovie(anyBoolean()))
.thenReturn(godfather);
Movie lionKing = mock(Movie.class);
when(
lionKing.getTitle(
anyString(), anyInt()))
.thenReturn("Lion King");
Rental lionKingRental =
mock(Rental.class);
when(
lionKingRental.getMovie(anyBoolean()))
.thenReturn(lionKing);
assertEquals(
"Recent rentals:\nGodfather 4\n" +
"Lion King",
a.customer.w(
godfatherRental, lionKingRental)
.build().recentRentals());
}
}
Default Return Values
public class CustomerTest {
@Test
public void recentRentalsWith2Rentals() {
Movie godfather = mock(Movie.class);
Rental godfatherRental =
mock(Rental.class);
when(
godfatherRental.getMovie(anyBoolean()))
.thenReturn(godfather);
Movie lionKing = mock(Movie.class);
Rental lionKingRental =
mock(Rental.class);
when(
lionKingRental.getMovie(anyBoolean()))
.thenReturn(lionKing);
assertEquals(
"Recent rentals:\nnull\nnull",
a.customer.w(
godfatherRental, lionKingRental)
.build().recentRentals());
}
}
public class CustomerTest {
@Test
public void recentRentalsWith2Rentals() {
Movie movie = mock(Movie.class);
Rental rental = mock(Rental.class);
when(rental.getMovie(anyBoolean()))
.thenReturn(movie);
assertEquals(
"Recent rentals:\nnull\nnull",
a.customer.w(rental, rental).build()
.recentRentals());
}
}
Law of Demeter
public class CustomerTest {
@Test
public void recentRentalsWith2Rentals() {
Rental rental = mock(Rental.class);
assertEquals(
"Recent rentals:\nnull\nnull",
a.customer.w(rental, rental).build()
.recentRentals());
}
}
public class Customer {
private String name;
private List<Rental> rentals =
new ArrayList<Rental>();
public Customer(String name) {
this.name = name;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String recentRentals() {
String result = "Recent rentals:";
for (int i=0;
i < rentals.size() && i < 3;
i++) {
result +=
"\n" + rentals.get(i).getTitle();
}
return result;
}
}
public class CustomerTest {
@Test
public void recentRentals0Rentals() {
assertEquals(
"Recent rentals:",
a.customer.build().recentRentals());
}
@Test
public void recentRentals1Rental() {
assertEquals(
"Recent rentals:\nnull",
a.customer.w(
mock(Rental.class)).build()
.recentRentals());
}
@Test
public void recentRentals2Rental() {
assertEquals(
"Recent rentals:\nnull\nnull",
a.customer.w(
mock(Rental.class),
mock(Rental.class)).build()
.recentRentals());
}
@Test
public void recentRentals3Rental() {
assertEquals(
"Recent rentals:\nnull\nnull\nnull",
a.customer.w(
mock(Rental.class),
mock(Rental.class),
mock(Rental.class)).build()
.recentRentals());
}
@Test
public void recentRentals4Rental() {
assertEquals(
"Recent rentals:\nnull\nnull\nnull",
a.customer.w(
mock(Rental.class),
mock(Rental.class),
mock(Rental.class),
mock(Rental.class)).build()
.recentRentals());
}
}
Get Sociable
public class CustomerTest {
@Test
public void
recentRentalsWith3OrderedRentals() {
assertEquals(
"Recent rentals:"+
"\nGodfather 4\nLion King\nScarface",
a.customer.w(
a.rental.w(a.movie.w("Godfather 4")),
a.rental.w(a.movie.w("Lion King")),
a.rental.w(a.movie.w("Scarface")),
a.rental.w(a.movie.w("Notebook")))
.build().recentRentals());
}
}
Comparison
The (Previously Unwritten) Original Test with Four Rentals
public class CustomerTest {
@Test
public void recentRentalsWith4Rentals() {
Movie godfather = mock(Movie.class);
when(godfather
.getTitle("%s starring %s %s", 2))
.thenReturn("Godfather 4");
Rental godfatherRental =
mock(Rental.class);
when(godfatherRental.getMovie(true))
.thenReturn(godfather);
Movie lionKing = mock(Movie.class);
when(lionKing
.getTitle("%s starring %s %s", 2))
.thenReturn("Lion King");
Rental lionKingRental =
mock(Rental.class);
when(lionKingRental.getMovie(true))
.thenReturn(lionKing);
Movie scarface = mock(Movie.class);
when(scarface
.getTitle("%s starring %s %s", 2))
.thenReturn("Scarface");
Rental scarfaceRental =
mock(Rental.class);
when(scarfaceRental.getMovie(true))
.thenReturn(scarface);
Movie notebook = mock(Movie.class);
when(notebook
.getTitle("%s starring %s %s", 2))
.thenReturn("Notebook");
Rental notebookRental =
mock(Rental.class);
when(notebookRental.getMovie(true))
.thenReturn(notebook);
assertEquals(
"Recent rentals:"+
"\nGodfather 4\nLion King" +
"\nScarface",
a.customer.w(
godfatherRental, lionKingRental,
scarfaceRental, notebookRental)
.build().recentRentals());
}
}
The Sociable Unit Test and The Sparsely Specified Solitary Unit Tests
public class CustomerTest {
@Test
public void
recentRentalsWith3OrderedRentals() {
assertEquals(
"Recent rentals:"+
"\nGodfather 4\nLion King\nScarface",
a.customer.w(
a.rental.w(a.movie.w("Godfather 4")),
a.rental.w(a.movie.w("Lion King")),
a.rental.w(a.movie.w("Scarface")),
a.rental.w(a.movie.w("Notebook")))
.build().recentRentals());
}
}
public class CustomerTest {
@Test
public void recentRentals0Rentals() {
assertEquals(
"Recent rentals:",
a.customer.build().recentRentals());
}
@Test
public void recentRentals1Rental() {
assertEquals(
"Recent rentals:\nnull",
a.customer.w(
mock(Rental.class)).build()
.recentRentals());
}
@Test
public void recentRentals2Rental() {
assertEquals(
"Recent rentals:\nnull\nnull",
a.customer.w(
mock(Rental.class),
mock(Rental.class)).build()
.recentRentals());
}
@Test
public void recentRentals3Rental() {
assertEquals(
"Recent rentals:\nnull\nnull\nnull",
a.customer.w(
mock(Rental.class),
mock(Rental.class),
mock(Rental.class)).build()
.recentRentals());
}
@Test
public void recentRentals4Rental() {
assertEquals(
"Recent rentals:\nnull\nnull\nnull",
a.customer.w(
mock(Rental.class),
mock(Rental.class),
mock(Rental.class),
mock(Rental.class)).build()
.recentRentals());
}
}
Assert Last
Expect Exceptions via Try/Catch
public class MovieTest {
@Test
(expected=IllegalArgumentException.class)
public void invalidTitle() {
a.movie.w(UNKNOWN).build();
}
}
public class MovieTest {
@Test
public void invalidTitle() {
try {
a.movie.w(UNKNOWN).build();
fail();
} catch (Exception ex) {
assertEquals(
IllegalArgumentException.class,
ex.getClass());
}
}
}
public class MovieTest {
@Test
public void invalidTitle() {
Exception e = null;
try {
a.movie.w(UNKNOWN).build();
} catch (Exception ex) {
e = ex;
}
assertEquals(
IllegalArgumentException.class,
e.getClass());
}
}
Assert Throws
public class MovieTest {
@Test
public void invalidTitle() {
Runnable runnable = new Runnable() {
public void run() {
a.movie.w(UNKNOWN).build();
}
};
assertThrows(
IllegalArgumentException.class,
runnable);
}
public void assertThrows(
Class ex, Runnable runnable) {
Exception exThrown = null;
try {
runnable.run();
} catch (Exception exThrownActual) {
exThrown = exThrownActual;
}
if (null == exThrown)
fail("No exception thrown");
else
assertEquals(ex, exThrown.getClass());
}
}
Mock Verification
Comparison
public class MovieTest {
Mockery context = new Mockery();
@Test
public void getPointsForDays() {
Movie movie = a.movie.build();
assertEquals(1, movie.getPoints(2));
assertEquals(1, movie.getPoints(3));
}
@Test
(expected=IllegalArgumentException.class)
public void invalidTitle() {
a.movie.w(UNKNOWN).build();
}
@Test
public void getPriceFromPriceInstance() {
final Price price =
context.mock(Price.class);
Movie movie = a.movie.build();
movie.setPrice(price);
context.checking(new Expectations() {{
oneOf(price).getCharge(3);
}});
movie.getCharge(3);
context.assertIsSatisfied();
}
}
public class MovieTest {
@Test
public void getPoints2Days() {
assertEquals(
2, a.movie.build().getPoints(2));
}
@Test
public void getPoints3Days() {
assertEquals(
2, a.movie.build().getPoints(3));
}
@Test
public void invalidTitle() {
Runnable runnable = new Runnable() {
public void run() {
a.movie.w(UNKNOWN).build();
}
};
assertThrows(
IllegalArgumentException.class,
runnable);
}
@Test
public void getPriceFromPriceInstance() {
Price price = mock(Price.class);
Movie movie = a.movie.build();
movie.setPrice(price);
movie.getCharge(3);
verify(price).getCharge(3);
}
}
Expect Literals
public class RegularPriceTest {
@Test
public void chargeWithStaticVal() {
assertEquals(
basePrice,
a.regularPrice.build().getCharge(2),
0);
}
@Test
public void chargeWithLocalVal() {
int daysRented = 4;
double charge =
basePrice + (
daysRented - 2) * multiplier;
assertEquals(
charge,
a.regularPrice.build().getCharge(
daysRented),
0);
}
@Test
public void chargeWithLiteral() {
assertEquals(
5.0,
a.regularPrice.build().getCharge(4),
0);
}
}
Value Objects vs Expect Literals
public class MovieTest {
@Test
public void compareDates() {
Movie godfather =
a.movie.w(
new Date(70261200000L)).build();
assertEquals(
"1972-03-24",
new SimpleDateFormat(
"yyyy-MM-dd").format(
godfather.releaseDate()));
}
}
Comparison
public class CustomerTest {
@Test
public void statementFor1Rental() {
Rental rental = mock(Rental.class);
Customer customer =
a.customer.w(rental).build();
assertEquals(
expStatement(
"Rental record for %s\n%sAmount " +
"owed is %s\n" +
"You earned %s frequent " +
"renter points",
customer,
rentalInfo(
"\t", "", new Rental[] {rental})),
customer.statement());
}
@Test
public void statementFor2Rentals() {
Rental godfather = mock(Rental.class);
Rental scarface = mock(Rental.class);
Customer customer =
a.customer.w(
godfather, scarface).build();
assertEquals(
expStatement(
"Rental record for %s\n%sAmount " +
"owed is %s\n" +
"You earned %s frequent " +
"renter points",
customer,
rentalInfo(
"\t", "", new Rental[] {
godfather, scarface})),
customer.statement());
}
public static String rentalInfo(
String startsWith,
String endsWith,
Rental[] rentals) {
String result = "";
for (Rental rental : rentals)
result += String.format(
"%s%s%s\n",
startsWith,
rental.getLineItem(),
endsWith);
return result;
}
public static String expStatement(
String formatStr,
Customer customer,
String rentalInfo) {
return String.format(
formatStr,
customer.getName(),
rentalInfo,
customer.getTotalCharge(),
customer.getTotalPoints());
}
}
public class CustomerTest {
@Test
public void statementFor1Rental() {
Customer customer =
a.customer.w(
mock(Rental.class)).build();
assertEquals(
"Rental record for Jim\n" +
"\tnull\n" +
"Amount owed is 0.0\n" +
"You earned 0 frequent renter points",
customer.statement());
}
@Test
public void statementFor2Rentals() {
Customer customer =
a.customer.w(
mock(Rental.class),
mock(Rental.class)).build();
assertEquals(
"Rental record for Jim\n"+
"\tnull\n" +
"\tnull\n" +
"Amount owed is 0.0\n" +
"You earned 0 frequent renter points",
customer.statement());
}
}
Negative Testing
public class RentalTest {
@Test
public void
storeMockNeverReceivesRemove() {
Movie movie = mock(Movie.class);
Rental rental =
a.rental.w(movie).build();
Store store = mock(Store.class);
when(
store.getAvailability(
any(Movie.class)))
.thenReturn(0);
rental.start(store);
verify(store, never()).remove(movie);
}
@Test
public void failOnStoreRemove() {
Movie movie = mock(Movie.class);
Rental rental =
a.rental.w(movie).build();
Store store = new Store(
new HashMap<Movie,Integer>()) {
public void remove(Movie movie) {
fail();
}
};
rental.start(store);
}
@Test
public void storeShouldNeverRemove() {
final boolean[] removeCalled = { false };
Movie movie = mock(Movie.class);
Rental rental =
a.rental.w(movie).build();
Store store = new Store(
new HashMap<Movie,Integer>()) {
public void remove(Movie movie) {
removeCalled[0] = true;
}
};
rental.start(store);
assertFalse(removeCalled[0]);
}
}
Strict Mocking
public class RentalTest {
@Test
public void verifyStoreInteractions() {
Movie movie = mock(Movie.class);
Rental rental =
a.rental.w(movie).build();
Store store = mock(Store.class);
rental.start(store);
verify(store).getAvailability(movie);
verifyNoMoreInteractions(store);
}
}
Just Be Sociable
public class RentalTest {
@Test
public void
storeAvailabilityIsModifiedOnRental() {
Movie movie = a.movie.build();
Rental rental =
a.rental.w(movie).build();
Store store =
a.store.w(movie, movie).build();
rental.start(store);
a.rental.build().start(store);
assertEquals(
1, store.getAvailability(movie));
}
@Test
public void
storeAvailabilityIsUnmodified() {
Movie movie = a.movie.build();
Rental rental =
a.rental.w(movie).build();
Store store = a.store.build();
rental.start(store);
assertEquals(
0, store.getAvailability(movie));
}
}
Hamcrest
Improving Test Cases
Too Much Magic
Self-shunt
public class RentalTest extends Store {
public static Movie movie =
mock(Movie.class);
private boolean removeCalled;
public RentalTest() {
super(new HashMap<Movie, Integer>() {{
this.put(movie, 2);
}});
}
@Test
public void removeIsCalled() {
Rental rental =
a.rental.w(movie).build();
rental.start(this);
assertEquals(true, removeCalled);
}
public void remove(Movie movie) {
super.remove(movie);
removeCalled = true;
}
}
Exceptional Success
public class RentalTest {
@Test(expected=RuntimeException.class)
public void removeIsCalled() {
final Movie movie = mock(Movie.class);
Rental rental =
a.rental.w(movie).build();
HashMap<Movie, Integer> movieMap =
new HashMap<Movie, Integer>() {{
this.put(movie, 2);
}};
Store store =
new Store(movieMap) {
public void remove(Movie movie) {
throw new
RuntimeException("success");
}
};
rental.start(store);
}
}
Inline Setup
public class CustomerTest {
Rental godfatherRental;
Rental lionKingRental;
Rental scarfaceRental;
Rental notebookRental;
Customer twoRentals;
Customer fourRentals;
@Before
public void init() {
godfatherRental = mock(Rental.class);
when(godfatherRental.getTitle())
.thenReturn("Godfather 4");
when(godfatherRental.getCharge())
.thenReturn(3.0);
when(godfatherRental.getPoints())
.thenReturn(2);
lionKingRental = mock(Rental.class);
when(lionKingRental.getTitle())
.thenReturn("Lion King");
when(lionKingRental.getCharge())
.thenReturn(2.0);
when(lionKingRental.getPoints())
.thenReturn(1);
scarfaceRental = mock(Rental.class);
when(scarfaceRental.getTitle())
.thenReturn("Scarface");
when(scarfaceRental.getCharge())
.thenReturn(1.0);
when(scarfaceRental.getPoints())
.thenReturn(1);
notebookRental = mock(Rental.class);
when(notebookRental.getTitle())
.thenReturn("Notebook");
when(notebookRental.getCharge())
.thenReturn(6.0);
when(notebookRental.getPoints())
.thenReturn(1);
twoRentals =
a.customer.w(
godfatherRental, lionKingRental)
.build();
fourRentals =
a.customer.w(
godfatherRental, lionKingRental,
scarfaceRental, notebookRental)
.build();
}
@Test
public void recentRentalsWith2Rentals() {
assertEquals(
"Recent rentals:"+
"\nGodfather 4\nLion King",
twoRentals.recentRentals());
}
@Test
public void recentRentalsWith4Rentals() {
assertEquals(
"Recent rentals:"+
"\nGodfather 4\nLion King\nScarface",
fourRentals.recentRentals());
}
@Test
public void totalChargeWith2Rentals() {
assertEquals(
5.0,
twoRentals.getTotalCharge(),
0);
}
@Test
public void totalChargeWith4Rentals() {
assertEquals(
12.0,
fourRentals.getTotalCharge(),
0);
}
@Test
public void totalPointsWith2Rentals() {
assertEquals(
3,
twoRentals.getTotalPoints());
}
@Test
public void totalPointsWith4Rentals() {
assertEquals(
5,
fourRentals.getTotalPoints());
}
@Test
public void getName() {
assertEquals(
"Jim", twoRentals.getName());
}
}
Similar Creation and Action
Obviousness
Setup As An Optimization
Comparison
public class CustomerTest {
Rental godfatherRental;
Rental lionKingRental;
Rental scarfaceRental;
Rental notebookRental;
Customer twoRentals;
Customer fourRentals;
@Before
public void init() {
godfatherRental = mock(Rental.class);
when(godfatherRental.getTitle())
.thenReturn("Godfather 4");
when(godfatherRental.getCharge())
.thenReturn(3.0);
when(godfatherRental.getPoints())
.thenReturn(2);
lionKingRental = mock(Rental.class);
when(lionKingRental.getTitle())
.thenReturn("Lion King");
when(lionKingRental.getCharge())
.thenReturn(2.0);
when(lionKingRental.getPoints())
.thenReturn(1);
scarfaceRental = mock(Rental.class);
when(scarfaceRental.getTitle())
.thenReturn("Scarface");
when(scarfaceRental.getCharge())
.thenReturn(1.0);
when(scarfaceRental.getPoints())
.thenReturn(1);
notebookRental = mock(Rental.class);
when(notebookRental.getTitle())
.thenReturn("Notebook");
when(notebookRental.getCharge())
.thenReturn(6.0);
when(notebookRental.getPoints())
.thenReturn(1);
twoRentals =
a.customer.w(
godfatherRental, lionKingRental)
.build();
fourRentals =
a.customer.w(
godfatherRental, lionKingRental,
scarfaceRental, notebookRental)
.build();
}
@Test
public void recentRentalsWith2Rentals() {
assertEquals(
"Recent rentals:"+
"\nGodfather 4\nLion King",
twoRentals.recentRentals());
}
@Test
public void recentRentalsWith4Rentals() {
assertEquals(
"Recent rentals:"+
"\nGodfather 4\nLion King\nScarface",
fourRentals.recentRentals());
}
@Test
public void totalChargeWith2Rentals() {
assertEquals(
5.0,
twoRentals.getTotalCharge(),
0);
}
@Test
public void totalChargeWith4Rentals() {
assertEquals(
12.0,
fourRentals.getTotalCharge(),
0);
}
@Test
public void totalPointsWith2Rentals() {
assertEquals(
3,
twoRentals.getTotalPoints());
}
@Test
public void totalPointsWith4Rentals() {
assertEquals(
5,
fourRentals.getTotalPoints());
}
@Test
public void getName() {
assertEquals(
"Jim", twoRentals.getName());
}
}
public class CustomerTest {
@Test
public void recentRentalsWith2Rentals() {
Rental godfatherRental =
mock(Rental.class);
when(godfatherRental.getTitle())
.thenReturn("Godfather 4");
Rental lionKingRental =
mock(Rental.class);
when(lionKingRental.getTitle())
.thenReturn("Lion King");
assertEquals(
"Recent rentals:"+
"\nGodfather 4\nLion King",
a.customer.w(
godfatherRental, lionKingRental)
.build().recentRentals());
}
@Test
public void recentRentalsWith4Rentals() {
Rental godfatherRental =
mock(Rental.class);
when(godfatherRental.getTitle())
.thenReturn("Godfather 4");
Rental lionKingRental =
mock(Rental.class);
when(lionKingRental.getTitle())
.thenReturn("Lion King");
Rental scarfaceRental =
mock(Rental.class);
when(scarfaceRental.getTitle())
.thenReturn("Scarface");
Rental notebookRental =
mock(Rental.class);
when(notebookRental.getTitle())
.thenReturn("Notebook");
assertEquals(
"Recent rentals:"+
"\nGodfather 4\nLion King\nScarface",
a.customer.w(
godfatherRental, lionKingRental,
scarfaceRental, notebookRental)
.build().recentRentals());
}
@Test
public void totalChargeWith2Rentals() {
Rental godfatherRental =
mock(Rental.class);
when(godfatherRental.getCharge())
.thenReturn(3.0);
Rental lionKingRental =
mock(Rental.class);
when(lionKingRental.getCharge())
.thenReturn(2.0);
assertEquals(
5.0,
a.customer.w(
godfatherRental, lionKingRental)
.build().getTotalCharge(),
0);
}
@Test
public void totalChargeWith4Rentals() {
Rental godfatherRental =
mock(Rental.class);
when(godfatherRental.getCharge())
.thenReturn(3.0);
Rental lionKingRental =
mock(Rental.class);
when(lionKingRental.getCharge())
.thenReturn(2.0);
Rental scarfaceRental =
mock(Rental.class);
when(scarfaceRental.getCharge())
.thenReturn(1.0);
Rental notebookRental =
mock(Rental.class);
when(notebookRental.getCharge())
.thenReturn(6.0);
assertEquals(
12.0,
a.customer.w(
godfatherRental, lionKingRental,
scarfaceRental, notebookRental)
.build().getTotalCharge(),
0);
}
@Test
public void totalPointsWith2Rentals() {
Rental godfatherRental =
mock(Rental.class);
when(godfatherRental.getPoints())
.thenReturn(2);
Rental lionKingRental =
mock(Rental.class);
when(lionKingRental.getPoints())
.thenReturn(1);
assertEquals(
3,
a.customer.w(
godfatherRental, lionKingRental)
.build().getTotalPoints());
}
@Test
public void totalPointsWith4Rentals() {
Rental godfatherRental =
mock(Rental.class);
when(godfatherRental.getPoints())
.thenReturn(2);
Rental lionKingRental =
mock(Rental.class);
when(lionKingRental.getPoints())
.thenReturn(1);
Rental scarfaceRental =
mock(Rental.class);
when(scarfaceRental.getPoints())
.thenReturn(1);
Rental notebookRental =
mock(Rental.class);
when(notebookRental.getPoints())
.thenReturn(1);
assertEquals(
5,
a.customer.w(
godfatherRental, lionKingRental,
scarfaceRental, notebookRental)
.build().getTotalPoints());
}
@Test
public void getName() {
assertEquals(
"Jim",
a.customer.build().getName());
}
}
Test Names
Improving Test Suites
Separating The Solitary From The Sociable
Increasing Consistency And Speed With Solitary Unit Tests
Database and Filesystem Interaction
public class PidWriter {
public static void writePid(
String filename,
RuntimeMXBean bean) {
try {
writePidtoFile(filename, bean);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void writePidtoFile(
String filename,
RuntimeMXBean bean) throws IOException {
FileWriter writer =
new FileWriter(filename);
try {
String runtimeName = bean.getName();
writer.write(
runtimeName.substring(
0, runtimeName.indexOf('@')));
}
finally {
writer.close();
}
}
}
public class PidWriterTest {
@Test
public void writePid() throws Exception {
RuntimeMXBean bean =
mock(RuntimeMXBean.class);
when(bean.getName()).thenReturn("12@X");
PidWriter.writePid(
"/tmp/sample.pid", bean);
assertEquals(
"12",
Files.readAllLines(
Paths.get("/tmp/sample.pid"),
Charset.defaultCharset()).get(0));
}
}
The Solitary Unit Test
public class FileWriterGateway
extends FileWriter {
public static boolean disallowAccess =
false;
public FileWriterGateway(
String filename) throws IOException {
super(filename);
if (disallowAccess) {
throw new RuntimeException(
"access disallowed");
}
}
}
public class PidWriter {
public static void writePid(
String filename,
RuntimeMXBean bean) {
try {
writePidtoFile(filename, bean);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void writePidtoFile(
String filename,
RuntimeMXBean bean) throws IOException {
FileWriterGateway writer =
new FileWriterGateway(filename);
try {
String runtimeName = bean.getName();
writer.write(
runtimeName.substring(
0, runtimeName.indexOf('@')));
}
finally {
writer.close();
}
}
}
public class PidWriterTest extends Solitary {
@Test
public void writePid() throws Exception {
RuntimeMXBean bean =
mock(RuntimeMXBean.class);
when(bean.getName()).thenReturn("12@X");
PidWriter.writePid(
"/tmp/sample.pid", bean);
assertEquals(
"12",
Files.readAllLines(
Paths.get("/tmp/sample.pid"),
Charset.defaultCharset()).get(0));
}
}
public class Solitary {
@Before
public void setup() {
FileWriterGateway.disallowAccess = true;
}
}
public class PidWriterTest extends Solitary {
@Test
public void writePid() throws Exception {
RuntimeMXBean bean =
mock(RuntimeMXBean.class);
when(bean.getName()).thenReturn("12@X");
FileWriterGateway facade =
mock(FileWriterGateway.class);
PidWriter.writePid(facade, bean);
verify(facade).write("12");
}
}
public class PidWriter {
public static void writePid(
String filename,
RuntimeMXBean bean) {
try {
FileWriterGateway writer =
new FileWriterGateway(filename);
writePid(writer, bean);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void writePid(
FileWriterGateway facade,
RuntimeMXBean bean) {
try {
writePidtoFile(facade, bean);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void writePidtoFile(
FileWriterGateway facade,
RuntimeMXBean bean) throws IOException {
try {
String runtimeName = bean.getName();
facade.write(
runtimeName.substring(
0, runtimeName.indexOf('@')));
} catch (IOException e) {
throw new RuntimeException(e);
}
finally {
facade.close();
}
}
}
The Sociable Unit Test
public class PidWriterTest extends Sociable {
@Test
public void writePid() throws Exception {
RuntimeMXBean bean =
mock(RuntimeMXBean.class);
when(bean.getName()).thenReturn("12@X");
PidWriter.writePid(
"/tmp/wewut/sample.pid", bean);
assertEquals(
"12",
Files.readAllLines(
Paths.get("/tmp/wewut/sample.pid"),
Charset.defaultCharset()).get(0));
}
}
public class Sociable {
@Before
public void setup()
throws Exception {
Process p;
p = Runtime.getRuntime().exec(
"rm -rf /tmp/wewut");
p.waitFor();
p = Runtime.getRuntime().exec(
"mkdir -p /tmp/wewut");
p.waitFor();
}
}
Revisiting Concerns
Time Interaction
public class Rental {
Movie movie;
private int daysRented;
private boolean started;
private DateTime creationDateTime;
public Rental(
Movie movie,
int daysRented,
DateTime creationDateTime) {
this.movie = movie;
this.daysRented = daysRented;
this.creationDateTime = creationDateTime;
}
public Rental(Movie movie, int daysRented) {
this(movie, daysRented, new DateTime());
}
public DateTime getCreationDateTime() {
return creationDateTime;
}
}
public class RentalTest {
@Test
public void creationDateTimeNow() {
DateTimeUtils.setCurrentMillisFixed(1000);
Rental rental = a.rental.build();
assertEquals(
1000,
rental.getCreationDateTime()
.getMillis());
}
@Test
public void creationDateTimeSet() {
Rental rental =
a.rental.w(
new DateTime(199)).build();
assertEquals(
199,
rental.getCreationDateTime()
.getMillis());
}
}
public class RentalTest {
@Test
public void creationDateTimeNow() {
DateTimeUtils.setCurrentMillisFixed(1000);
Rental rental = a.rental.build();
assertEquals(
1000,
rental.getCreationDateTime()
.getMillis());
DateTimeUtils.setCurrentMillisSystem();
}
@Test
public void creationDateTimeSet() {
Rental rental =
a.rental.w(
new DateTime(199)).build();
assertEquals(
199,
rental.getCreationDateTime()
.getMillis());
}
}
public class Solitary {
@Before
public void setup() {
FileWriterGateway.disallowAccess = true;
DateTimeUtils.setCurrentMillisFixed(1000);
}
}
public class RentalTest extends Solitary {
@Test
public void creationDateTimeNow() {
Rental rental = a.rental.build();
assertEquals(
1000,
rental.getCreationDateTime()
.getMillis());
}
@Test
public void creationDateTimeSet() {
Rental rental =
a.rental.w(
new DateTime(199)).build();
assertEquals(
199,
rental.getCreationDateTime()
.getMillis());
}
}
Using Speed To Your Advantage
Avoiding Cascading Failures With Solitary Unit Tests
public class CustomerTest {
@Test
public void noRentalsStatement() {
assertEquals(
"Rental record for Jim\nAmount owed " +
"is 0.0\n" +
"You earned 0 frequent renter points",
a.customer.build().statement());
}
@Test
public void oneRentalStatement() {
assertEquals(
"Rental record for Jim\n" +
"\tGodfather 4 9.0\n" +
"Amount owed is 9.0\n" +
"You earned 2 frequent renter points",
a.customer.w(
a.rental).build().statement());
}
@Test
public void twoRentalsStatement() {
assertEquals(
"Rental record for Jim\n" +
"\tGodfather 4 9.0\n" +
"\tGodfather 4 9.0\n" +
"Amount owed is 18.0\n" +
"You earned 4 frequent renter points",
a.customer.w(
a.rental, a.rental).build()
.statement());
}
@Test
public void noRentalsGetTotalPoints() {
assertEquals(
0,
a.customer.build().getTotalPoints());
}
@Test
public void oneRentalGetTotalPoints() {
assertEquals(
2,
a.customer.w(
a.rental).build().getTotalPoints());
}
@Test
public void twoRentalsGetTotalPoints() {
assertEquals(
4,
a.customer.w(a.rental, a.rental)
.build()
.getTotalPoints());
}
// 3 tests for htmlStatement()
// left to the imagination
}
public class RentalTest {
@Test
public void getPointsFromMovie() {
assertEquals(
2, a.rental.build().getPoints());
}
}
public class MovieTest {
@Test
public void getPoints() {
assertEquals(
2, a.movie.build().getPoints(2));
}
}
public class NewReleasePrice extends Price {
@Override
public double getCharge(int daysRented) {
return daysRented * 3;
}
@Override
public int getPoints(int daysRented) {
if (daysRented > 1)
return 3; // was 2
return 1;
}
}
Class Under Test
public class CustomerTest {
@Test
public void noRentalsStatement() {
assertEquals(
"Rental record for Jim\nAmount owed " +
"is 0.0\n" +
"You earned 0 frequent renter points",
a.customer.build().statement());
}
@Test
public void oneRentalStatement() {
Rental rental = mock(Rental.class);
when(rental.getLineItem())
.thenReturn("Godfather 4 9.0");
when(rental.getCharge())
.thenReturn(9.0);
when(rental.getPoints())
.thenReturn(2);
assertEquals(
"Rental record for Jim\n" +
"\tGodfather 4 9.0\n" +
"Amount owed is 9.0\n" +
"You earned 2 frequent renter points",
a.customer.w(rental).build()
.statement());
}
@Test
public void twoRentalsStatement() {
Rental one = mock(Rental.class);
when(one.getLineItem())
.thenReturn("Godfather 4 9.0");
when(one.getCharge())
.thenReturn(9.0);
when(one.getPoints())
.thenReturn(2);
Rental two = mock(Rental.class);
when(two.getLineItem())
.thenReturn("Godfather 4 9.0");
when(two.getCharge())
.thenReturn(9.0);
when(two.getPoints())
.thenReturn(2);
assertEquals(
"Rental record for Jim\n" +
"\tGodfather 4 9.0\n" +
"\tGodfather 4 9.0\n" +
"Amount owed is 18.0\n" +
"You earned 4 frequent renter points",
a.customer.w(one, two).build()
.statement());
}
@Test
public void noRentalsGetTotalPoints() {
assertEquals(
0,
a.customer.build().getTotalPoints());
}
@Test
public void oneRentalGetTotalPoints() {
Rental rental = mock(Rental.class);
when(rental.getPoints())
.thenReturn(2);
assertEquals(
2,
a.customer.w(
rental).build().getTotalPoints());
}
@Test
public void twoRentalsGetTotalPoints() {
Rental one = mock(Rental.class);
when(one.getPoints())
.thenReturn(2);
Rental two = mock(Rental.class);
when(two.getPoints())
.thenReturn(3);
assertEquals(
5,
a.customer.w(
one, two).build().getTotalPoints());
}
}
public class RentalTest {
@Test
public void getPointsFromMovie() {
Movie movie = mock(Movie.class);
when(movie.getPoints(2))
.thenReturn(2);
assertEquals(
2,
a.rental.w(
2).w(movie).build().getPoints());
}
}
public class Rental {
Movie movie;
private int daysRented;
private boolean started;
public Rental(
Movie movie, int daysRented) {
this.movie = movie;
this.daysRented = daysRented;
}
public double getCharge() {
return movie.getCharge(daysRented);
}
public int getPoints() {
return
movie.getPoints(daysRented, false);
}
public int getPoints(boolean vipFlag) {
return
movie.getPoints(daysRented, vipFlag);
}
public String getLineItem() {
return
movie.getTitle() + " " + getCharge();
}
}
public class CustomerTest {
@Test
public void noRentalsStatement() {
assertEquals(
"Rental record for Jim\nAmount owed " +
"is 0.0\n" +
"You earned 0 frequent renter points",
a.customer.build().statement());
}
@Test
public void oneRentalStatement() {
Rental rental = mock(Rental.class);
assertEquals(
"Rental record for Jim\n\tnull\n" +
"Amount owed is 0.0\n" +
"You earned 0 frequent renter points",
a.customer.w(
rental).build().statement());
}
@Test
public void twoRentalsStatement() {
Rental rental = mock(Rental.class);
assertEquals(
"Rental record for Jim\n\tnull\n" +
"\tnull\nAmount owed is 0.0\n" +
"You earned 0 frequent renter points",
a.customer.w(
rental, rental).build().statement());
}
@Test
public void noRentalsGetTotalPoints() {
assertEquals(
0,
a.customer.build().getTotalPoints());
}
@Test
public void oneRentalGetTotalPoints() {
Rental rental = mock(Rental.class);
when(rental.getPoints())
.thenReturn(2);
assertEquals(
2,
a.customer.w(
rental).build().getTotalPoints());
}
@Test
public void twoRentalsGetTotalPoints() {
Rental one = mock(Rental.class);
when(one.getPoints())
.thenReturn(2);
Rental two = mock(Rental.class);
when(two.getPoints())
.thenReturn(3);
assertEquals(
5,
a.customer.w(
one, two).build().getTotalPoints());
}
}
Revisiting the Definition of Solitary Unit Test
public class MovieTest {
@Test
public void getChargeForChildrens() {
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(1),
0);
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(2),
0);
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(3),
0);
assertEquals(
3.0,
a.movie.w(
CHILDREN).build().getCharge(4),
0);
assertEquals(
4.5,
a.movie.w(
CHILDREN).build().getCharge(5),
0);
}
}
public class Movie {
public enum Type {
REGULAR, NEW_RELEASE, CHILDREN, UNKNOWN;
}
private String title;
Price price;
public Movie(
String title, Movie.Type priceCode) {
this.title = title;
setPriceCode(priceCode);
}
public String getTitle() {
return title;
}
private void setPriceCode(
Movie.Type priceCode) {
switch (priceCode) {
case CHILDREN:
price = new ChildrensPrice();
break;
case NEW_RELEASE:
price = new NewReleasePrice();
break;
case REGULAR:
price = new RegularPrice();
break;
default:
throw new IllegalArgumentException(
"invalid price code");
}
}
public double getCharge(int daysRented) {
return price.getCharge(daysRented);
}
public int getPoints(int daysRented) {
return price.getPoints(daysRented);
}
}
public class ChildrensPrice extends Price {
@Override
public double getCharge(int daysRented) {
double amount = 1.5;
if (daysRented > 2) // *was 3*
amount += (daysRented - 2) * 1.5;
return amount;
}
}
Questionable Tests
Testing Language Features or Standard Library Classes
public class JavaTest {
@Test
public void arrayListGet() {
ArrayList<Integer> list =
new ArrayList<Integer>();
list.add(1);
assertEquals(
Integer.valueOf(1), list.get(0));
}
@Test
public void hashMapGet() {
HashMap<Integer, String> map =
new HashMap<Integer, String>();
map.put(1, "a str");
assertEquals("a str", map.get(1));
}
@Test
public void throwCatch() {
Exception ex = null;
try {
throw new RuntimeException("ex");
} catch (Exception eCaught) {
ex = eCaught;
}
assertEquals("ex", ex.getMessage());
}
}
Testing Framework Features or Classes
public class JodaTest {
@Test
public void parseStr() {
assertEquals(
286347600000L,
DateTime.parse(
"1979-01-28").getMillis());
}
}
Testing Private Methods
Custom Assertions
public class Assert {
public static void assertThrows(
Class ex, Runnable runnable) {
Exception exThrown = null;
try {
runnable.run();
} catch (Exception exThrownActual) {
exThrown = exThrownActual;
}
if (null == exThrown)
fail("No exception thrown");
else
assertEquals(ex, exThrown.getClass());
}
}
public class MovieTest {
@Test
public void invalidTitleCustomAssertion() {
assertThrows(
IllegalArgumentException.class,
() -> a.movie.w(UNKNOWN).build());
}
@Test
public void invalidTitleWithoutCA() {
Exception e = null;
try {
a.movie.w(UNKNOWN).build();
} catch (Exception ex) {
e = ex;
}
assertEquals(
IllegalArgumentException.class,
e.getClass());
}
}
public class AssertTest {
@Test
public void failIfNoThrow() {
AssertionError e = null;
try {
assertThrows(
IllegalArgumentException.class,
mock(Runnable.class));
} catch (AssertionError ex) {
e = ex;
}
assertEquals(
AssertionError.class,
e.getClass());
}
@Test
public void failWithMessageIfNoThrow() {
AssertionError e = null;
try {
assertThrows(
IllegalArgumentException.class,
mock(Runnable.class));
} catch (AssertionError ex) {
e = ex;
}
assertEquals(
"No exception thrown",
e.getMessage());
}
@Test
public void failIfClassMismatch() {
AssertionError e = null;
try {
assertThrows(
IllegalArgumentException.class,
() -> {
throw new RuntimeException("");});
} catch (AssertionError ex) {
e = ex;
}
assertEquals(
AssertionError.class,
e.getClass());
}
@Test
public void failWithMessageIfClassWrong() {
AssertionError e = null;
try {
assertThrows(
IllegalArgumentException.class,
() -> {
throw new RuntimeException("");});
} catch (AssertionError ex) {
e = ex;
}
assertEquals(
"expected:<class java.lang."+
"IllegalArgumentException> "+
"but was:<class java.lang."+
"RuntimeException>",
e.getMessage());
}
@Test
public void passWithCorrectException() {
AssertionError e = null;
try {
assertThrows(
RuntimeException.class,
() -> {
throw new RuntimeException("");});
} catch (AssertionError ex) {
e = ex;
}
assertEquals(null, e);
}
}
Custom Assertions on Value Objects
public class MovieTest {
@Test
public void compareDates() {
Movie godfather =
a.movie.w(
new Date(70261200000L)).build();
assertEquals(
"1972-03-24",
new SimpleDateFormat(
"yyyy-MM-dd").format(
godfather.releaseDate()));
}
}
public class Assert {
public static void assertDateWithFormat(
String expected,
String format,
Date dt) {
assertEquals(
expected,
new SimpleDateFormat(
format).format(dt));
}
}
public class MovieTest {
@Test
public void compareDates() {
Movie godfather =
a.movie.w(
new Date(70261200000L)).build();
assertDateWithFormat(
"1972-03-24",
"yyyy-MM-dd",
godfather.releaseDate());
}
}
Custom Assertions for Money
public class Money {
private BigDecimal val;
public Money(double val) {
this(BigDecimal.valueOf(val));
}
public Money(BigDecimal val) {
this.val = val;
}
public Money add(double d) {
return new Money(
val.add(BigDecimal.valueOf(d)));
}
public Money add(Money m) {
return new Money(val.add(m.val));
}
public double toDouble() {
return val
.setScale(2, BigDecimal.ROUND_HALF_UP)
.doubleValue();
}
}
public class MoneyTest {
@Test
public void doubleAddition() {
assertEquals(
11.0,
a.money.w(1.0).build().add(
10.0).toDouble(),
0);
}
@Test
public void moneyAddition() {
assertEquals(
11.0,
a.money.w(1.0).build().add(
a.money.w(10.0).build()).toDouble(),
0);
}
@Test
public void oneDecimalToDouble() {
assertEquals(
1.0,
a.money.w(1.0).build().toDouble(),
0);
}
@Test
public void twoDecimalToDouble() {
assertEquals(
1.12,
a.money.w(1.12).build().toDouble(),
0);
}
@Test
public void thrDecimalUpToDouble() {
assertEquals(
1.12,
a.money.w(1.123).build().toDouble(),
0);
}
@Test
public void thrDecimalDownToDouble() {
assertEquals(
1.13,
a.money.w(1.125).build().toDouble(),
0);
}
}
public class Movie {
public enum Type {
REGULAR, NEW_RELEASE, CHILDREN;
}
private String title;
Price price;
public Movie(
String title, Movie.Type priceCode) {
this.title = title;
setPriceCode(priceCode);
}
private void setPriceCode(
Movie.Type priceCode) {
switch (priceCode) {
case CHILDREN:
price = new ChildrensPrice();
break;
case NEW_RELEASE:
price = new NewReleasePrice();
break;
case REGULAR:
price = new RegularPrice();
break;
}
}
public Money getCharge(int daysRented) {
return price.getCharge(daysRented);
}
}
public class MovieTest {
@Test
public void getChargeForChildrens1Day() {
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(
1).toDouble(),
0);
}
@Test
public void getChargeForChildrens2Day() {
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(
2).toDouble(),
0);
}
@Test
public void getChargeForChildrens3Day() {
assertEquals(
1.5,
a.movie.w(
CHILDREN).build().getCharge(
3).toDouble(),
0);
}
@Test
public void getChargeForChildrens4Day() {
assertEquals(
3.0,
a.movie.w(
CHILDREN).build().getCharge(
4).toDouble(),
0);
}
@Test
public void getChargeForChildrens5Day() {
assertEquals(
4.5,
a.movie.w(
CHILDREN).build().getCharge(
5).toDouble(),
0);
}
}
public class Assert {
public static void assertMoney(
double d, Money m) {
assertEquals(d, m.toDouble(), 0);
}
}
public class MoneyTest {
@Test
public void doubleAddition() {
assertMoney(
11.0, a.money.w(1.0).build().add(10.0));
}
@Test
public void moneyAddition() {
assertMoney(
11.0,
a.money.w(1.0).build().add(
a.money.w(10.0).build()));
}
@Test
public void oneDecimalToDouble() {
assertMoney(
1.0, a.money.w(1.0).build());
}
@Test
public void twoDecimalToDouble() {
assertMoney(
1.12, a.money.w(1.12).build());
}
@Test
public void thrDecimalUpToDouble() {
assertMoney(
1.12, a.money.w(1.123).build());
}
@Test
public void thrDecimalDownToDouble() {
assertMoney(
1.13, a.money.w(1.125).build());
}
}
public class MovieTest {
@Test
public void getChargeForChildrens1Day() {
assertMoney(
1.5,
a.movie.w(
CHILDREN).build().getCharge(1));
}
@Test
public void getChargeForChildrens2Day() {
assertMoney(
1.5,
a.movie.w(
CHILDREN).build().getCharge(2));
}
@Test
public void getChargeForChildrens3Day() {
assertMoney(
1.5,
a.movie.w(
CHILDREN).build().getCharge(3));
}
@Test
public void getChargeForChildrens4Day() {
assertMoney(
3.0,
a.movie.w(
CHILDREN).build().getCharge(4));
}
@Test
public void getChargeForChildrens5Day() {
assertMoney(
4.5,
a.movie.w(
CHILDREN).build().getCharge(5));
}
}
public class Rental {
Movie movie;
private int daysRented;
public Rental(
Movie movie, int daysRented) {
this.movie = movie;
this.daysRented = daysRented;
}
public Money getCharge() {
return movie.getCharge(daysRented);
}
}
public class RentalTest {
@Test
public void getChargeFromMovie() {
Movie movie = mock(Movie.class);
when(movie.getCharge(any(Integer.class)))
.thenReturn(a.money.w(1.5).build());
assertMoney(
1.5,
a.rental.w(movie).build().getCharge());
}
}
public class Customer {
private List<Rental> rentals =
new ArrayList<Rental>();
public void addRental(Rental rental) {
rentals.add(rental);
}
public Money getTotalCharge() {
Money total = new Money(0.0);
for (Rental rental : rentals)
total = total.add(rental.getCharge());
return total;
}
}
public class CustomerTest {
@Test
public void chargeForNoRentals() {
assertMoney(
0.0,
a.customer.build().getTotalCharge());
}
@Test
public void chargeForOneRental() {
Rental rental = mock(Rental.class);
when(rental.getCharge())
.thenReturn(a.money.w(2.0).build());
assertMoney(
2.0,
a.customer.w(
rental).build().getTotalCharge());
}
@Test
public void chargeForTwoRentals() {
Rental rental1 = mock(Rental.class);
when(rental1.getCharge())
.thenReturn(a.money.w(2.2).build());
Rental rental2 = mock(Rental.class);
when(rental2.getCharge())
.thenReturn(a.money.w(3.5).build());
assertMoney(
5.7,
a.customer.w(
rental1,
rental2).build().getTotalCharge());
}
}
Global Definition
Creating Domain Objects Within Tests
New Is The New New
Object Mother
Test Data Builders
Test Data Builder Syntax
Test Data Builder Guidelines Revisited
public class a {
public static CustomerBuilder customer =
new CustomerBuilder();
public static MoneyBuilder money =
new MoneyBuilder();
public static class CustomerBuilder {
Rental[] rentals;
CustomerBuilder() {
this(new Rental[0]);
}
CustomerBuilder(Rental[] rentals) {
this.rentals = rentals;
}
public CustomerBuilder w(
Rental... rentals) {
return new CustomerBuilder(rentals);
}
public Customer build() {
Customer result = new Customer();
for (Rental rental : rentals) {
result.addRental(rental);
}
return result;
}
}
public static class MoneyBuilder {
final double val;
MoneyBuilder() {
this(1.0);
}
MoneyBuilder(double val) {
this.val = val;
}
public MoneyBuilder w(double val) {
return new MoneyBuilder(val);
}
public Money build() {
return new Money(val);
}
}
}
public class CustomerTest {
@Test
public void chargeForTwoRentals() {
Rental rental1 = mock(Rental.class);
when(rental1.getCharge())
.thenReturn(a.money.w(2.2).build());
Rental rental2 = mock(Rental.class);
when(rental2.getCharge())
.thenReturn(a.money.w(3.5).build());
assertMoney(
5.7,
a.customer.w(
rental1,
rental2).build().getTotalCharge());
}
}
public class CustomerTest {
@Test
public void chargeForTwoRentals() {
Rental rental1 = mock(Rental.class);
when(rental1.getCharge())
.thenReturn(a.money.w(2.2).build());
Rental rental2 = mock(Rental.class);
when(rental2.getCharge())
.thenReturn(a.money.w(3.5).build());
Customer customer = a.customer.build();
customer.addRental(rental1);
customer.addRental(rental2);
assertMoney(
5.7, customer.getTotalCharge());
}
}
public class Customer {
private List<Rental> rentals =
new ArrayList<Rental>();
public void addRental(Rental rental) {
rentals.add(rental);
}
public Money getTotalCharge() {
Money total = new Money(0.0);
for (Rental rental : rentals)
total = total.add(rental.getCharge());
return total;
}
}
public class Customer {
private ArrayList<Rental> rentals =
new ArrayList<Rental>();
public Customer addRentals(
Rental... newRentals) {
rentals.addAll(Arrays.asList(newRentals));
return this;
}
public Money getTotalCharge() {
Money total = new Money(0.0);
for (Rental rental : rentals)
total = total.add(rental.getCharge());
return total;
}
}
public class CustomerTest {
@Test
public void chargeForTwoRentals() {
Rental rental1 = mock(Rental.class);
when(rental1.getCharge())
.thenReturn(a.money.w(2.2).build());
Rental rental2 = mock(Rental.class);
when(rental2.getCharge())
.thenReturn(a.money.w(3.5).build());
assertMoney(
5.7,
a.customer.build().addRentals(
rental1, rental2).getTotalCharge());
}
}
Creating Stubs
Create, Stub, Return
public class MockitoExtensions {
@SuppressWarnings("unchecked")
public static <T> T create(
Object methodCall) {
when(methodCall)
.thenReturn(
StubBuilder.current.returnValue);
return (T)
StubBuilder.current.mockInstance;
}
public static <T> StubBuilder<T> stub(
Class<T> klass) {
return new StubBuilder<T>(mock(klass));
}
public static class StubBuilder<T> {
public static StubBuilder current;
public final T mockInstance;
private Object returnValue;
public StubBuilder(T mockInstance) {
current = this;
this.mockInstance = mockInstance;
}
public T from() {
return mockInstance;
}
public StubBuilder<T> returning(
Object returnValue) {
this.returnValue = returnValue;
return this;
}
}
}
public class CustomerTest {
@Test
public void chargeForTwoRentals() {
assertMoney(
5.7,
a.customer.build().addRentals(
create(
stub(Rental.class)
.returning(a.money.w(2.2).build())
.from().getCharge()),
create(
stub(Rental.class)
.returning(a.money.w(3.5).build())
.from().getCharge()))
.getTotalCharge());
}
}
Create, Lambda, Return
public class MockitoExtensions {
public static <T> T stub(
Class<T> klass,
Function<T,Object> f,
Object returnVal) {
try {
T result = mock(klass);
when(f.apply(result))
.thenReturn(returnVal);
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class CustomerTest {
@Test
public void chargeForTwoRentals() {
assertMoney(
5.7,
a.customer.build().addRentals(
stub(Rental.class,
s -> s.getCharge(),
a.money.w(2.2).build()),
stub(Rental.class,
s -> s.getCharge(),
a.money.w(3.5).build()))
.getTotalCharge());
}
}
More Than Creation
Closing Thoughts
Broad Stack Tests
Test Pyramid
Final Thoughts On ROI
More...
Foreword
Preface
Why Test?
Who Should Read This Book
Building on the Foundations Laid by Others
More...