Skip to content

Commit

Permalink
New assertions in RecordedRequestAssertions (#534)
Browse files Browse the repository at this point in the history
* hasPath(String pathTemplate, Object... arguments)
* hasBodySatisfying(Consumer<String> bodyConsumer)
* hasJsonBodyWithEntity(Object entity)
* hasJsonBodyWithEntity(Object entity, ObjectMapper objectMapper)
* hasJsonBodyWithEntity(Object entity, JsonHelper jsonHelper)

Closes #533
  • Loading branch information
sleberknight authored Dec 24, 2024
1 parent 813b3d3 commit 416e21f
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package org.kiwiproject.test.okhttp3.mockwebserver;

import static org.kiwiproject.base.KiwiPreconditions.checkArgumentNotNull;
import static org.kiwiproject.base.KiwiPreconditions.requireNotNull;
import static org.kiwiproject.base.KiwiStrings.f;
import static org.kiwiproject.test.constants.KiwiTestConstants.JSON_HELPER;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import okhttp3.TlsVersion;
import okhttp3.mockwebserver.RecordedRequest;
import org.assertj.core.api.Assertions;
import org.kiwiproject.json.JsonHelper;

import java.io.IOException;
import java.net.URI;
Expand Down Expand Up @@ -214,6 +219,21 @@ public RecordedRequestAssertions hasPath(String path) {
return this;
}

/**
* Asserts the recorded request has the expected path.
* <p>
* Uses {@link org.kiwiproject.base.KiwiStrings#f(String, Object...)}
* to produce the expected path from the template and arguments.
*
* @param pathTemplate the template to use when constructing the expected path
* @param arguments the arguments for the template
* @return this instance
*/
public RecordedRequestAssertions hasPath(String pathTemplate, Object... arguments) {
var expectedPath = f(pathTemplate, arguments);
return hasPath(expectedPath);
}

/**
* Asserts the recorded request has the expected header name and value.
*
Expand Down Expand Up @@ -268,6 +288,74 @@ public RecordedRequestAssertions hasBody(String body) {
return this;
}

/**
* Asserts the recorded request has a body that satisfies assertions in the given consumer.
*
* @param bodyConsumer a {@link Consumer} containing one or more assertions on the request body
* @return this instance
*/
public RecordedRequestAssertions hasBodySatisfying(Consumer<String> bodyConsumer) {
checkMethodAllowsBody();

var bodyBuffer = recordedRequest.getBody();
var actualBodyUtf8 = bodyBuffer.readUtf8();
bodyConsumer.accept(actualBodyUtf8);

return this;
}

/**
* Asserts the recorded request has a JSON body that deserializes to the given entity.
*
* @param entity the expected request entity
* @return this instance
* @implNote uses {@link org.kiwiproject.test.constants.KiwiTestConstants#JSON_HELPER KiwiTestConstants#JSON_HELPER}
* to deserialize JSON. You can use the overloaded methods if you need more control over the JSON deserialization.
* @see #hasJsonBodyWithEntity(Object, ObjectMapper)
* @see #hasJsonBodyWithEntity(Object, JsonHelper)
*/
public RecordedRequestAssertions hasJsonBodyWithEntity(Object entity) {
return hasJsonBodyWithEntity(entity, JSON_HELPER);
}

/**
* Asserts the recorded request has a JSON body that deserializes to the given entity.
*
* @param entity the expected request entity
* @param objectMapper the Jackson {@link ObjectMapper} to use for deserializing JSON
* @return this instance
*/
public RecordedRequestAssertions hasJsonBodyWithEntity(Object entity, ObjectMapper objectMapper) {
return hasJsonBodyWithEntity(entity, new JsonHelper(objectMapper));
}

/**
* Asserts the recorded request has a JSON body that deserializes to the given entity.
*
* @param entity the expected request entity
* @param jsonHelper the {@link JsonHelper} to use for deserializing JSON
* @return this instance
*/
public RecordedRequestAssertions hasJsonBodyWithEntity(Object entity, JsonHelper jsonHelper) {
checkMethodAllowsBody();
checkArgumentNotNull(entity, "entity must not be null");

var bodyBuffer = recordedRequest.getBody();
var actualBodyUtf8 = bodyBuffer.readUtf8();

var jsonDetectionResult = jsonHelper.detectJson(actualBodyUtf8);
Assertions.assertThat(jsonDetectionResult.isJson())
.describedAs("Body content expected to be JSON")
.isTrue();

var actualEntity = jsonHelper.toObject(actualBodyUtf8, entity.getClass());
Assertions.assertThat(actualEntity)
.usingRecursiveComparison()
.isEqualTo(entity);

return this;
}

/**
* Asserts the recorded request has a body of the given size.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import okio.Buffer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
Expand All @@ -22,6 +23,7 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.kiwiproject.base.UncheckedInterruptedException;
import org.kiwiproject.test.constants.KiwiTestConstants;

import java.io.IOException;
import java.net.ConnectException;
Expand Down Expand Up @@ -181,12 +183,82 @@ void shouldCheckMethodCanHaveBody(String method) {
);
}

@Test
void shouldCheckBody_WhenSatisfiesConsumer() {
assertThatCode(() ->
assertThatRecordedRequest(recordedRequest)
.hasBodySatisfying(theBody -> {
assertThat(theBody).contains("alice");
assertThat(theBody).contains("peaches");
})).doesNotThrowAnyException();
}

@Test
void shouldCheckBody_WhenDoesNotSatisfyConsumer() {
assertThatThrownBy(() ->
assertThatRecordedRequest(recordedRequest)
.hasBodySatisfying(theBody -> {
assertThat(theBody).contains("alice");
assertThat(theBody).contains("apples");
}))
.hasMessageContaining("Expecting")
.hasMessageContaining("alice")
.hasMessageContaining("peaches");
}

@Test
void shouldCheckInvalidBodySize() {
assertThatThrownBy(() -> RecordedRequestAssertions.assertThat(recordedRequest).hasBodySize(2_567))
.isNotNull()
.hasMessageContaining("Expected body size: 2567 byte");
}

@Test
void shouldCheckHasJsonBodyWithEntity() {
var expectedEntity = new UserCredentials("alice", "peaches");
assertThatCode(() ->
assertThatRecordedRequest(recordedRequest).hasJsonBodyWithEntity(expectedEntity)
).doesNotThrowAnyException();
}

@Test
void shouldCheckHasJsonBodyWithEntity_WithCustomObjectMapper() {
var expectedEntity = new UserCredentials("alice", "peaches");
var objectMapper = KiwiTestConstants.OBJECT_MAPPER;
assertThatCode(() ->
assertThatRecordedRequest(recordedRequest).hasJsonBodyWithEntity(expectedEntity, objectMapper)
).doesNotThrowAnyException();
}

@Test
void shouldCheckHasJsonBodyWithEntity_WhenDoesNotMatchExpectedEntity() {
var expectedEntity = new UserCredentials("alice", "oranges");
assertThatThrownBy(() ->
assertThatRecordedRequest(recordedRequest).hasJsonBodyWithEntity(expectedEntity))
.hasMessageContaining("Expecting actual")
.hasMessageContaining(new UserCredentials("alice", "peaches").toString())
.hasMessageContaining("to be equal to")
.hasMessageContaining(expectedEntity.toString())
;
}

@Test
void shouldCheckHasJsonBodyWithEntity_WhenBodyIsNotJson() {
var badRecordedRequest = mock(RecordedRequest.class);
when(badRecordedRequest.getMethod()).thenReturn("POST");

var buffer = mock(Buffer.class);
when(badRecordedRequest.getBody()).thenReturn(buffer);
when(buffer.readUtf8()).thenReturn("this is not json");

var expectedEntity = new UserCredentials("alice", "peaches");
assertThatThrownBy(() ->
assertThatRecordedRequest(badRecordedRequest).hasJsonBodyWithEntity(expectedEntity))
.hasMessageContaining("Body content expected to be JSON");
}
}

record UserCredentials(String username, String password) {
}

@Nested
Expand Down Expand Up @@ -544,6 +616,37 @@ void shouldCheckInvalidFailureCauseInstanceOf() {
}
}

@Nested
class HasPath {

@Test
void shouldPass_WhenGivenTemplateAndValidArguments() {
server.enqueue(new MockResponse());
JdkHttpClients.get(httpClient, uri("/users/42"));

var recordedRequest = takeRequest();

var assertions = RecordedRequestAssertions.assertThat(recordedRequest);

assertThatCode(() -> assertions.hasPath("/users/{}", 42))
.doesNotThrowAnyException();
}

@Test
void shouldFailsWhenGiven_TemplateAndInvalidArguments() {
server.enqueue(new MockResponse());
JdkHttpClients.get(httpClient, uri("/users/42"));

var recordedRequest = takeRequest();

var assertions = RecordedRequestAssertions.assertThat(recordedRequest);

assertThatThrownBy(() -> assertions.hasPath("/users/{}", 84))
.isNotNull()
.hasMessageContaining("Expected path to be: /users/84");
}
}

private RecordedRequest takeRequest() {
try {
return server.takeRequest(10, TimeUnit.MILLISECONDS);
Expand Down

0 comments on commit 416e21f

Please sign in to comment.