Test for Data contract list
src/test/resources/djmil/cashcard/list.json
[
{ "id": 99, "amount": 123.45 },
{ "id": 100, "amount": 1.0 },
{ "id": 101, "amount": 150.0 }
]
Now open the CashCardsJsonTest.java
file. And add cashCards
class-level variable to contain the following Java array:
private CashCard[] cashCards;
@BeforeEach
void setUp() {
cashCards = Arrays.array(
new CashCard(99L, 123.45),
new CashCard(100L, 100.00),
new CashCard(101L, 150.00));
}
Than add the new test:
@Test
void cashCardListSerializationTest() throws IOException {
assertThat(jsonList.write(cashCards)).isStrictlyEqualToJson("list.json");
}
@Test
void cashCardListDeserializationTest() throws IOException {
String expected="""
[
{ "id": 99, "amount": 123.45 },
{ "id": 100, "amount": 100.00 },
{ "id": 101, "amount": 150.00 }
]
""";
assertThat(jsonList.parse(expected)).isEqualTo(cashCards);
}
GET list
DB data
It is important to populate our test db with known data, that would reflect desired test case scenario. src/test/resource/data.sql
INSERT INTO CASH_CARD(ID, AMOUNT) VALUES (99, 123.45);
INSERT INTO CASH_CARD(ID, AMOUNT) VALUES (100, 1.00);
INSERT INTO CASH_CARD(ID, AMOUNT) VALUES (101, 150.00);
Endpoint Test
In src/test/java/djmil/cashcard/CashCardApplication.test
@Test
void shouldReturnAllCashCardsWhenListIsRequested() {
ResponseEntity<String> response = restTemplate.getForEntity("/cashcards", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
DocumentContext documentContext = JsonPath.parse(response.getBody());
int cashCardCount = documentContext.read("$.length()");
assertThat(cashCardCount).isEqualTo(3);
JSONArray ids = documentContext.read("$..id");
assertThat(ids).containsExactlyInAnyOrder(99, 100, 101);
JSONArray amounts = documentContext.read("$..amount");
assertThat(amounts).containsExactlyInAnyOrder(123.45, 100.0, 150.00);
}
Check out these new JsonPath expressions! A good place to learn more about JsonPath is the JsonPath documentation:
documentContext.read("$.length()")
calculates the length of the array..read("$..id")
retrieves the list of allid
values returned, while.read("$..amount")
collects allamounts
returned.
Controller code
src/main/java/djmil/cashcard/CashCardController.java
@GetMapping()
public ResponseEntity<Iterable<CashCard>> findAll() {
return ResponseEntity.ok(cashCardRepository.findAll());
}
Test Interaction and @DirtiesContext
Depending on test execution order schedule, our new shouldReturnAllCashCardsWhenListIsRequested
test might not pass! Why?
[~/exercises] $ ./gradlew test
...
org.opentest4j.AssertionFailedError:
expected: 3
but was: 4
...
at app//example.cashcard.CashCardApplicationTests.shouldReturnAllCashCardsWhenListIsRequested(CashCardApplicationTests.java:70)
The reason is that one of the other tests is interfering with our new test by creating a new Cash Card. @DirtiesContext
fixes this problem by causing Spring to start with a clean slate, as if those other tests hadn't been run. Removing it (commenting it out) from the class caused our new test to fail.
Although you can use @DirtiesContext
to work around inter-test interaction, you shouldn't use it indiscriminately; you should have a good reason. Our reason here is to clean up after creating a new Cash Card.
Leave DirtiesContext
commented out at the class level, and uncomment it on the method which creates a new Cash Card:
//@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
class CashCardApplicationTests {
...
@Test
@DirtiesContext
void shouldCreateANewCashCard() {
...
GET pagination
Test
Since we have 3 CashCards
in our Pagination#DB data, Let's set up a test to fetch them one at a time (page size of 1), then have their amounts sorted from highest to lowest (descending).
@Test
void shouldReturnASortedPageOfCashCards() {
ResponseEntity<String> response = restTemplate.getForEntity("/cashcards?page=0&size=1&sort=amount,desc", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
DocumentContext documentContext = JsonPath.parse(response.getBody());
JSONArray read = documentContext.read("$[*]");
assertThat(read.size()).isEqualTo(1);
double amount = documentContext.read("$[0].amount");
assertThat(amount).isEqualTo(150.00);
}
The URI we are requesting contains both pagination and sorting information: /cashcards?page=0&size=1&sort=amount,desc
:
page=0
: Get the first page. Page indexes start at 0.size=1
: Each page has size 1.sort=amount,desc
Controller
@GetMapping
public ResponseEntity<List<CashCard>> findAll(Pageable pageable) {
Page<CashCard> page = cashCardRepository.findAll(
PageRequest.of(
pageable.getPageNumber(),
pageable.getPageSize(),
pageable.getSort()
));
return ResponseEntity.ok(page.getContent());
}
Pagination with defaults
Spring provides the default page
and size
values (they are 0 and 20, respectively). A default of 20 for page size explains why all three of our Cash Cards were returned. Again: we didn't need to explicitly define these defaults. Spring provides them "out of the box". So the test with
ResponseEntity<String> response = restTemplate.getForEntity("/cashcards", String.class);
shall work. But there are no reasonable defaults for sorting order possible. So if the one was not provided, query would return items in order defined by DB (most likely in order of creation). To deal with this limitation, we have to define the default sort
parameter in our own code, by passing a Sort
object to getSortOr()
:
PageRequest.of(
pageable.getPageNumber(),
pageable.getPageSize(),
pageable.getSortOr(Sort.by(Sort.Direction.ASC, "amount"))
));