Everyone should write unit tests. And I mean EVERYONE. Integration tests are very popular and useful but unit tests are the fastest. Having good unit tests allows you to check whether your class works within second or two. UTs also tell you that something's wrong but how they do it ?
When test fails you know that some parts of your code don't work as you expect. You see the name of failing test and a class which the test corresponds to so you have general idea what might have gone wrong but if you want to fix that bug you have to read both test and the code. This is why your tests should be extremely readable.
I use the following live template for all the tests I write (Intellij Idea):
@Test
public void $METHOD_NAME$() throws Exception {
// given
$END$
// when
// then
}
When section invokes a method which is being tested by particular test.
Given section prepares the input and records mocks' behaviour.
Then section checks the output.
I really like given-when-then template. It clearly separates those three sections so you always know which object is the input and so on.
When section typically contains single line of code.
Actually I like when each section is one-liner but it's usually impossible.
Perfect test for me looks basically like that:
@Test
public void should_calculate_ceiling() throws Exception {
// given
double price = 7.8;
// when
double ceiling = Math.ceil(price);
// then
assertThat(ceiling).isEqualTo(8.0);
}
That case is actually too simple because typically you have to check the state of some object.
Let's say that you're testing some class that returns an instance of Person:
@AllArgsConstructor
@Getter
public static class Person {
private final String firstName;
private final String lastName;
private final int age;
private final Sex sex;
private final Optional<Job> job;
public static enum Sex {
MALE, FEMALE
}
public static class Job {}
}
I would never implement Person class like this but it's just an example so forgive me that :)
If you use standard junit assertions you would probably write something like that:
@Test
public void should_return_joey() throws Exception {
// when
Person person = p();
// then
assertEquals(Sex.MALE, person.getSex());
assertEquals("Joey", person.getFirstName());
assertEquals("Tribiani", person.getLastName());
assertTrue(person.getAge() > 18);
assertEquals(Optional.empty(), person.getJob());
}
private Person p() {
return new Person("Joey", "Tribiani", 25, Sex.MALE, Optional.empty());
}
I really don't like assertEquals(). First of all you have to remember order of parameters. The first one is expected and the second actual. If you make mistake you will see a misleading message.
Another thing assertTrue() doesn't tell you why Joey has to be older than 18.
This is why you should use some library that contains fluent assertions and allows you to create your own assertions. The most popular assertions library for Java is probably AssertJ.
Having for instance a string you can check many things at once:
assertThat("Joey Tribiani").hasSize(13)
.startsWith("Jo")
.contains("y T")
.endsWith("ani")
.doesNotContain("Rachel");
Same applies to collections:
List<String> friends = ImmutableList.of("Rachel", "Ross", "Joey", "Chandler", "Pheebs", "Monica");
assertThat(friends).hasSize(6)
.doesNotContain("Gunther")
.containsSequence("Rachel", "Ross")
.endsWith("Monica")
.allMatch(friend -> friend.matches("[A-Z][a-z]*"));
And so on...
Ok let's write custom assertions for Person class.
First of all you have to create a class that extends AbstractAssert
public class PersonAssertions extends AbstractAssert<PersonAssertions, Person> {
}
Then we must add constructor that matches super() and static method assertThat:
public class PersonAssertions extends AbstractAssert<PersonAssertions, Person> {
public static PersonAssertions assertThat(final Person actual) {
return new PersonAssertions(actual);
}
private PersonAssertions(final Person actual) {
super(actual, PersonAssertions.class);
}
}
Now we can start adding assertions.
First of all let's create some methods that allow you to check first and last name.
public PersonAssertions hasFirstName(final String name) {
Assertions.assertThat(name).isEqualTo(actual.getFirstName());
return this;
}
public PersonAssertions hasLastName(final String lastName) {
Assertions.assertThat(lastName).isEqualTo(actual.getLastName());
return this;
}
Note that we always return this to make the assertions fluent.
Now:
assertEquals("Joey", person.getFirstName());
assertEquals("Tribiani", person.getLastName());
Can be replaced by:
assertThat(person).hasFirstName("Joey")
.hasLastName("Tribiani");
Now let's check gender:
public PersonAssertions isFemale() {
Assertions.assertThat(actual.getSex()).isSameAs(Sex.FEMALE);
return this;
}
public PersonAssertions isMale() {
Assertions.assertThat(actual.getSex()).isSameAs(Sex.MALE);
return this;
}
Now we have:
assertThat(person).hasFirstName("Joey")
.hasLastName("Tribiani")
.isMale();
Those assertions are just fancy methods that check internal state of given object. All the created methods are way more readable but we can still add more specific assertions that suits to our domain.
Let's assume that we're selling alcohol.... In Poland you can buy alcohol if you're 18 years old.
public PersonAssertions canBuyBeerInPoland() {
Assertions.assertThat(actual.getAge()).isGreaterThanOrEqualTo(18);
return this;
}
assertThat(person).hasFirstName("Joey")
.hasLastName("Tribiani")
.isMale()
.canBuyBeerInPoland();
canBuyBeerInPoland() looks much better than assertTrue(person.getAge() > 18) because it uses our domain language.
And the last one. Let's check whether given person is unemployed. Person class contains Optional<Job> so we have to check if it's empty or not:
public PersonAssertions isUnemployed() {
Assertions.assertThat(actual.getJob()).isEmpty();
return this;
}
Now you can compare junit assertions to our custom assertj assertions:
JUnit:
assertEquals(Sex.MALE, person.getSex());
assertEquals("Joey", person.getFirstName());
assertEquals("Tribiani", person.getLastName());
assertTrue(person.getAge() > 18);
assertEquals(Optional.empty(), person.getJob());
AssertJ:
assertThat(person).hasFirstName("Joey")
.hasLastName("Tribiani")
.isMale()
.canBuyBeerInPoland()
.isUnemployed();
It looks much better and you can reuse all those methods.
You should also think about overriding error messages. We're still using the same instance of Person:
private Person p() {
return new Person("Joey", "Tribiani", 25, Sex.MALE, Optional.empty());
}
The follwoing assertion:
assertThat(person).hasFirstName("Joeya");
generates error message like this:
org.junit.ComparisonFailure:
Expected :"Joey"
Actual :"Joeya"
You can always override error message like that:
public PersonAssertions hasFirstName(final String name) {
Assertions.assertThat(name).overridingErrorMessage("Given Person [" + actual + "] has name " + actual.getFirstName() + ". Expected: " + name)
.isEqualTo(actual.getFirstName());
return this;
}
Now the same test generates:
java.lang.AssertionError: Given Person [DistributorSalesScheduler.Person(firstName=Joey, lastName=Tribiani, age=25, sex=MALE, job=Optional.empty)] has name Joey. Expected: Joeya
The whole class looks as follows:
public static class PersonAssertions extends AbstractAssert<PersonAssertions, Person> {
public static PersonAssertions assertThat(final Person actual) {
return new PersonAssertions(actual);
}
private PersonAssertions(final Person actual) {
super(actual, PersonAssertions.class);
}
public PersonAssertions hasFirstName(final String name) {
Assertions.assertThat(name).overridingErrorMessage("Given Person [" + actual + "] has name " + actual.getFirstName() + ". Expected: " + name)
.isEqualTo(actual.getFirstName());
return this;
}
public PersonAssertions hasLastName(final String lastName) {
Assertions.assertThat(lastName).isEqualTo(actual.getLastName());
return this;
}
public PersonAssertions canBuyBeerInPoland() {
Assertions.assertThat(actual.getAge()).isGreaterThanOrEqualTo(18);
return this;
}
public PersonAssertions isFemale() {
Assertions.assertThat(actual.getSex()).isSameAs(Sex.FEMALE);
return this;
}
public PersonAssertions isMale() {
Assertions.assertThat(actual.getSex()).isSameAs(Sex.MALE);
return this;
}
public PersonAssertions hasJob() {
Assertions.assertThat(actual.getJob()).isPresent();
return this;
}
public PersonAssertions isUnemployed() {
Assertions.assertThat(actual.getJob()).isEmpty();
return this;
}
}
As you see writing custom assertions is very easy and makes tests more readable. All the assertions are reusable and can be unit tested. The project I've been working on contains module with all the utilities for tests so other modules can import the assertions. I'm always very happy when I see that someone's created new set of assertions for our domain objects :)