Testing Top-Level Functions

The Airport class we designed using tests has the facility to return the information about a single airport and to sort a list of airports by name. We need a function that will take a list of airport codes and return a sorted list of airport information. We’ll implement that function in a new AirportStatus.kt file, as a top-level function rather than as a method of a class. It turns out testing a top-level function isn’t any different than testing a method of a class, as we’ll see soon.

In the spirit of growing code incrementally using tests, let’s first implement a simple, synchronous version of a getAirportStatus() function that takes a list of airport codes and returns a sorted list of Airport instances filled with information about each relevant airport. Let’s look at the test first.

We’ll use the first test to tease out the signature of the method getAirportStatus()—it should take a list of airport codes and return a list of Airports. Very quickly we’ll need a few more tests for different combinations of airport codes. When writing the first test, we’ll prepare for that by writing a data-driven test. Let’s create a file named AirportStatusTest.kt under the src/test/kotlin/com/agiledeveloper/airportstatus directory with the following code:

 package​ ​com.agiledeveloper.airportstatus
 
 import​ ​io.kotlintest.specs.StringSpec
 import​ ​io.kotlintest.shouldBe
 import​ ​io.kotlintest.data.forall
 import​ ​io.kotlintest.tables.row
 import​ ​io.kotlintest.TestCase
 import​ ​io.kotlintest.TestResult
 import​ ​io.mockk.*
 
 class​ AirportStatusTest : StringSpec() {
  init {
 "getAirportStatus returns status for airports in sorted order"​ {
  forall(
  row(listOf<String>(), listOf<Airport>())
  ) { input, result ->
  getAirportStatus(input) shouldBe result
  }
  }
  }
 }

In the test, within the argument passed to the forall function, we define a pair of input and expected output using the row() function. Then in the lambda attached to forall we invoke the function under test, pass the first value from the row, and expect the result to be the second value from the row. Right now, the only expectation we have for the getAirportStatus() function is that it takes a list of String and returns an empty list of Airports. Let’s get that implementation in place. To pass this test, create a file named AirportStatus.kt under the directory src/main/kotlin/com/agiledeveloper/airportstatus and add the following code:

 package​ ​com.agiledeveloper.airportstatus
 
 fun​ ​getAirportStatus​(airportCodes: List<String>): List<Airport> = listOf()

Next, we’ll add another row to the test, with one airport code in the input list of airport codes and an expected Airport information.

 forall(
  row(listOf<String>(), listOf<Airport>()),
  row(listOf(​"IAD"​), listOf(iad))
 ) { input, result ->
  getAirportStatus(input) shouldBe result
  }

Two issues arise in running this test. First, we need to define the reference iad provided in the second row. Second, to make this test to pass, if we change getAirportStatus() so that it calls Airport.getAirportData(), the test will fail since the latter function calls fetchData(), which isn’t implemented yet. To keep our focus on the design and implementation of getAirportStatus() we’ll mock the Airport.getAirportData() function, in the AirportStatusTest class, like so:

 val​ iad = Airport(​"IAD"​, ​"Dulles"​, ​true​)
 val​ iah = Airport(​"IAH"​, ​"Houston"​, ​false​)
 val​ inv = Airport(​"inv"​, ​"Invalid Airport"​, ​false​)
 
 override​ ​fun​ ​beforeTest​(testCase: TestCase) {
  mockkObject(Airport)
  every { Airport.getAirportData(​"IAD"​) } returns iad
  every { Airport.getAirportData(​"IAH"​) } returns iah
  every { Airport.getAirportData(​"inv"​) } returns inv
 }
 
 override​ ​fun​ ​afterTest​(testCase: TestCase, result: TestResult) {
  clearAllMocks()
 }

We’ve defined three fields in the test, each referring to canned airport information. Within the every() functions, we instruct the mock for Airport to return the appropriate canned value based on the airport code passed to the Airport.getAirportData() function.

To make the test pass, edit the getAirportStatus() function to return a list of Airport for the given list of airport codes.

 fun​ ​getAirportStatus​(airportCodes: List<String>): List<Airport> =
  airportCodes.map { code -> Airport.getAirportData(code) }

Using the map() function, we iterate over each element in the input airportCodes collection and create a list of Airport instances returned by the getAirportData() function.

Let’s add a few more rows of test data, where the rows we’ll add will expect the result from getAirportStatus() to be in sorted order by name of the airport.

 forall(
  row(listOf<String>(), listOf<Airport>()),
  row(listOf(​"IAD"​), listOf(iad)),
  row(listOf(​"IAD"​, ​"IAH"​), listOf(iad, iah)),
  row(listOf(​"IAH"​, ​"IAD"​), listOf(iad, iah)),
  row(listOf(​"inv"​, ​"IAD"​, ​"IAH"​), listOf(iad, iah, inv))
 ) { input, result ->
  getAirportStatus(input) shouldBe result
  }

To make the modified test pass, we’ll pass the result of the call to map() through the Airport.sort() function.

 fun​ ​getAirportStatus​(airportCodes: List<String>): List<Airport> =
  Airport.sort(
  airportCodes.map { code -> Airport.getAirportData(code) })

Run the tests and verify that all tests pass.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.34.87