Test Driven Development with JUnit 5. Part 5

5. Introducing new features using TDD

The first new features that we will implement are a new flight type-premium-and policies concerning this flight type. There is a policy for adding a passenger: if the passenger is a VIP, the passenger should be added to the premium flight; otherwise, the request must be rejected. There is also a policy for removing a passenger: if required, a passenger may be removed from a flight.

We would like to take increased advantage of working TDD style and do more refactoring-this time, to the tests. This is in the spirit of theRule of Three, as stated by Don Roberts.

The first time you do something, you just do it. The second time you do something similar, you wince at the duplication, but you do the duplicate thing anyway. The third time you do something similar, you refactor.

So, three strikes and you refactor.

After receiving the requirement for the implementation of this third flight type, it is time to do some more grouping of the existing tests using the JUnit 5 @Nested annotation and then implement the premium flight requirement in a similar way. Following is the refactored AirportTest class before moving to the work for the premium flight.

public class AirportTest {

@DisplayName(“Given there is an economy flight”)

@Nested

class EconomyFlightTest {

private Flight economyFlight; #1

private Passenger mike; #1

private Passenger james; #1

@BeforeEach

void setUp() {

economyFlight = new EconomyFlight(“1”); #2

mike = new Passenger(“Mike”, false); #2

james = new Passenger(“James”, true); #2

}

@Nested #3

@DisplayName(“When we have a regular passenger”) #3

class RegularPassenger { #3

@Test

@DisplayName(

“Then you can add and remove him from an economy flight”) #4

public void testEconomyFlightRegularPassenger() {

assertAll( #5

“Verify all conditions for a regular passenger #5

and an economy flight”, #5

() -> assertEquals(“1”, economyFlight.getId()), #5

() -> assertEquals(true, #5

economyFlight.addPassenger(mike)), #5

() -> assertEquals(1, #5

economyFlight.getPassengersList().size()), #5

() -> assertEquals(“Mike”, #5

economyFlight.getPassengersList() #5

.get(0).getName()), #5

() -> assertEquals(true, #5

economyFlight.removePassenger(mike)), #5

() -> assertEquals(0, #5

economyFlight.getPassengersList().size()) #5

); #5

}

}

@Nested #3

@DisplayName(“When we have a VIP passenger”) #3

class VipPassenger { #3

@Test

@DisplayName(“Then you can add him but #4

cannot remove him from an economy flight”) #4

public void testEconomyFlightVipPassenger() {

assertAll(“Verify all conditions for a VIP passenger #5

and an economy flight”, #5

() -> assertEquals(“1”, economyFlight.getId()), #5

() -> assertEquals(true, #5

economyFlight.addPassenger(james)), #5

() -> assertEquals(1, #5

economyFlight.getPassengersList().size()), #5

() -> assertEquals(“James”, #5

economyFlight.getPassengersList().get(0).getName()), #5

() -> assertEquals(false, #5

economyFlight.removePassenger(james)), #5

() -> assertEquals(1, #5

economyFlight.getPassengersList().size()) #5

);

}

}

}

@DisplayName(“Given there is a business flight”)

@Nested

class BusinessFlightTest {

private Flight businessFlight; #1

private Passenger mike; #1

private Passenger james; #1

@BeforeEach

void setUp() {

businessFlight = new BusinessFlight(“2”); #2

mike = new Passenger(“Mike”, false); #2

james = new Passenger(“James”, true); #2

}

@Nested #3

@DisplayName(“When we have a regular passenger”) #3

class RegularPassenger { #3

@Test

@DisplayName(“Then you cannot add or remove him #4

from a business flight”) #4

public void testBusinessFlightRegularPassenger() {

assertAll(“Verify all conditions for a regular passenger #5

and a business flight”, #5

() -> assertEquals(false, #5

businessFlight.addPassenger(mike)), #5

() -> assertEquals(0, #5

businessFlight.getPassengersList().size()), #5

() -> assertEquals(false, #5

businessFlight.removePassenger(mike)), #5

() -> assertEquals(0, #5

businessFlight.getPassengersList().size()) #5

);

}

}

@Nested #3

@DisplayName(“When we have a VIP passenger”) #3

class VipPassenger { #3

@Test

@DisplayName(“Then you can add him but cannot remove him #4

from a business flight”) #4

public void testBusinessFlightVipPassenger() {

assertAll(“Verify all conditions for a VIP passenger #5

and a business flight”, #5

() -> assertEquals(true, #5

businessFlight.addPassenger(james)), #5

() -> assertEquals(1, #5

businessFlight.getPassengersList().size()), #5

() -> assertEquals(false, #5

businessFlight.removePassenger(james)), #5

() -> assertEquals(1, #5

businessFlight.getPassengersList().size()) #5

);

}

}

}

}

In this listing:

  • In the existing nested classes EconomyFlightTest and BusinessFlightTest, we group the flight and passenger fields, as we would like to add one more testing level and reuse these fields for all tests concerning a particular flight type #1. We initialize these fields before the execution of each test #2.
  • We introduce a new nesting level to test different passenger types. We use the JUnit 5 @DisplayName annotation to label the classes in a way that is more expressive and easier to follow #3. All of these labels start with the keyword When.
  • We label all existing tests with the help of the JUnit 5 @DisplayName annotation #4. All of these labels start with the keyword Then.
  • We refactor the checking of the conditions by using the assertAll JUnit 5 method and grouping all previously existing conditions, which can now be read fluently #5.

This is how we refactored the existing tests, to facilitate continuing to work in TDD style and to introduce the newly required premium flight business logic. If we run the tests now, we can easily follow the way they work and how they check the business logic. Any new developer joining the project will find these tests extremely valuable as part of the documentation!

Interested in JUnit? Check out our trainings.

Catalin Tudose
Java and Web Technologies Expert

Originally published at https://www.luxoft-training.com.