Skip to content

Commit

Permalink
test: use assertj (#6)
Browse files Browse the repository at this point in the history
* adr: Fluent assertions
* test: Refactor tests
  • Loading branch information
guillermocalvo authored Feb 28, 2024
1 parent 3a11792 commit 4a5845f
Show file tree
Hide file tree
Showing 29 changed files with 363 additions and 283 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-params")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")

// AssertJ
testImplementation("org.assertj:assertj-core:3.25.1")

// Logging
testRuntimeOnly("ch.qos.logback:logback-classic")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package org.projectcheckins.core;

import static org.assertj.core.api.Assertions.assertThat;

import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import org.projectcheckins.core.idgeneration.IdGenerator;

import java.util.HashSet;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertEquals;

@MicronautTest
class IdGeneratorTest {

Expand All @@ -20,6 +20,6 @@ void testGenerate(IdGenerator idGenerator) {
String id = idGenerator.generate();
ids.add(id);
}
assertEquals(expectedSize, ids.size());
assertThat(ids).hasSize(expectedSize);
}
}
20 changes: 13 additions & 7 deletions core/src/test/java/org/projectcheckins/core/QuestionSaveTest.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
package org.projectcheckins.core;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;

import io.micronaut.core.type.Argument;
import io.micronaut.serde.SerdeIntrospections;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import io.micronaut.validation.validator.Validator;
import org.junit.jupiter.api.Test;
import org.projectcheckins.core.forms.QuestionSave;

import static org.junit.jupiter.api.Assertions.*;

@MicronautTest
class QuestionSaveTest {

@Test
void titleIsRequired(Validator validator) {
assertFalse(validator.validate(new QuestionSave(null)).isEmpty());
assertFalse(validator.validate(new QuestionSave("")).isEmpty());
assertTrue(validator.validate(new QuestionSave("What are you working on")).isEmpty());
assertThat(validator.validate(new QuestionSave(null)))
.isNotEmpty();
assertThat(validator.validate(new QuestionSave("")))
.anyMatch(x -> x.getPropertyPath().toString().equals("title") && x.getMessage().equals("must not be blank"));
assertThat(validator.validate(new QuestionSave("What are you working on")))
.isEmpty();
}

@Test
void questionSaveIsAnnotatedWithSerdeableDeserializable(SerdeIntrospections serdeIntrospections) {
assertDoesNotThrow(() -> serdeIntrospections.getDeserializableIntrospection(Argument.of(QuestionSave.class)));
assertThatCode(() -> serdeIntrospections.getDeserializableIntrospection(Argument.of(QuestionSave.class)))
.doesNotThrowAnyException();
}

@Test
void questionSaveIsAnnotatedWithSerdeableSerializable(SerdeIntrospections serdeIntrospections) {
assertDoesNotThrow(() -> serdeIntrospections.getSerializableIntrospection(Argument.of(QuestionSave.class)));
assertThatCode(() -> serdeIntrospections.getSerializableIntrospection(Argument.of(QuestionSave.class)))
.doesNotThrowAnyException();
}
}
29 changes: 19 additions & 10 deletions core/src/test/java/org/projectcheckins/core/QuestionTest.java
Original file line number Diff line number Diff line change
@@ -1,38 +1,47 @@
package org.projectcheckins.core;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;

import io.micronaut.core.type.Argument;
import io.micronaut.serde.SerdeIntrospections;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import io.micronaut.validation.validator.Validator;
import org.junit.jupiter.api.Test;
import org.projectcheckins.core.forms.Question;

import static org.junit.jupiter.api.Assertions.*;

@MicronautTest
class QuestionTest {

@Test
void idCannotBeBlank(Validator validator) {
assertFalse(validator.validate(new Question(null, "What are you working on")).isEmpty());
assertFalse(validator.validate(new Question("", "What are you working on")).isEmpty());
assertTrue(validator.validate(new Question("xxx", "What are you working on")).isEmpty());
assertThat(validator.validate(new Question(null, "What are you working on")))
.isNotEmpty();
assertThat(validator.validate(new Question("", "What are you working on")))
.anyMatch(x -> x.getPropertyPath().toString().equals("id") && x.getMessage().equals("must not be blank"));
assertThat(validator.validate(new Question("xxx", "What are you working on")))
.isEmpty();
}

@Test
void titleCannotBeBlank(Validator validator) {
assertFalse(validator.validate(new Question("xxx", null)).isEmpty());
assertFalse(validator.validate(new Question("xxx", "")).isEmpty());
assertTrue(validator.validate(new Question("xxx", "What are you working on")).isEmpty());
assertThat(validator.validate(new Question("xxx", null)))
.isNotEmpty();
assertThat(validator.validate(new Question("xxx", "")))
.anyMatch(x -> x.getPropertyPath().toString().equals("title") && x.getMessage().equals("must not be blank"));
assertThat(validator.validate(new Question("xxx", "What are you working on")))
.isEmpty();
}

@Test
void questionSaveIsAnnotatedWithSerdeableDeserializable(SerdeIntrospections serdeIntrospections) {
assertDoesNotThrow(() -> serdeIntrospections.getDeserializableIntrospection(Argument.of(Question.class)));
assertThatCode(() -> serdeIntrospections.getDeserializableIntrospection(Argument.of(Question.class)))
.doesNotThrowAnyException();
}

@Test
void questionSaveIsAnnotatedWithSerdeableSerializable(SerdeIntrospections serdeIntrospections) {
assertDoesNotThrow(() -> serdeIntrospections.getSerializableIntrospection(Argument.of(Question.class)));
assertThatCode(() -> serdeIntrospections.getSerializableIntrospection(Argument.of(Question.class)))
.doesNotThrowAnyException();
}
}
29 changes: 19 additions & 10 deletions core/src/test/java/org/projectcheckins/core/QuestionUpdateTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.projectcheckins.core;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;

import io.micronaut.core.type.Argument;
import io.micronaut.serde.SerdeIntrospections;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
Expand All @@ -8,32 +11,38 @@
import org.projectcheckins.core.forms.Question;
import org.projectcheckins.core.forms.QuestionUpdate;

import static org.junit.jupiter.api.Assertions.*;

@MicronautTest
class QuestionUpdateTest {

@Test
void idCannotBeBlank(Validator validator) {
assertFalse(validator.validate(new QuestionUpdate(null, "What are you working on")).isEmpty());
assertFalse(validator.validate(new QuestionUpdate("", "What are you working on")).isEmpty());
assertTrue(validator.validate(new QuestionUpdate("xxx", "What are you working on")).isEmpty());
assertThat(validator.validate(new QuestionUpdate(null, "What are you working on")))
.isNotEmpty();
assertThat(validator.validate(new QuestionUpdate("", "What are you working on")))
.anyMatch(x -> x.getPropertyPath().toString().equals("id") && x.getMessage().equals("must not be blank"));
assertThat(validator.validate(new QuestionUpdate("xxx", "What are you working on")))
.isEmpty();
}

@Test
void titleCannotBeBlank(Validator validator) {
assertFalse(validator.validate(new QuestionUpdate("xxx", null)).isEmpty());
assertFalse(validator.validate(new QuestionUpdate("xxx", "")).isEmpty());
assertTrue(validator.validate(new QuestionUpdate("xxx", "What are you working on")).isEmpty());
assertThat(validator.validate(new QuestionUpdate("xxx", null)))
.isNotEmpty();
assertThat(validator.validate(new QuestionUpdate("xxx", "")))
.anyMatch(x -> x.getPropertyPath().toString().equals("title") && x.getMessage().equals("must not be blank"));
assertThat(validator.validate(new QuestionUpdate("xxx", "What are you working on")))
.isEmpty();
}

@Test
void questionSaveIsAnnotatedWithSerdeableDeserializable(SerdeIntrospections serdeIntrospections) {
assertDoesNotThrow(() -> serdeIntrospections.getDeserializableIntrospection(Argument.of(QuestionUpdate.class)));
assertThatCode(() -> serdeIntrospections.getDeserializableIntrospection(Argument.of(QuestionUpdate.class)))
.doesNotThrowAnyException();
}

@Test
void questionSaveIsAnnotatedWithSerdeableSerializable(SerdeIntrospections serdeIntrospections) {
assertDoesNotThrow(() -> serdeIntrospections.getSerializableIntrospection(Argument.of(Question.class)));
assertThatCode(() -> serdeIntrospections.getSerializableIntrospection(Argument.of(Question.class)))
.doesNotThrowAnyException();
}
}
31 changes: 31 additions & 0 deletions doc/adr/0019-fluent-assertions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 19. Fluent Assertions

Date: 2024-02-28
Creator: Guillermo Calvo
Reviewer: Sergio del Amo


## Status

Accepted


## Context

We would like to make the development of tests as easy as possible.

This will allow us to keep a high code coverage percentage to avoid obvious errors.

Fluent assertions provide a lightway DSL that can save us from most boilerplate code.


## Decision

We will use [AssertJ](https://assertj.github.io/doc/) consistently in all parts of the project where it makes sense.


## Consequences

- Code that tests parts of the application will be shorter, more concise, and easier to read.
- We will be able to have more tests to cover more scenarios with less effort.
- Tests that fail will yield a meaningful error message.
57 changes: 38 additions & 19 deletions http/src/test/java/org/projectcheckins/http/AssertUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,56 @@
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;

import java.util.Optional;
import java.util.function.Predicate;

import static org.junit.jupiter.api.Assertions.*;

public final class AssertUtils {
private AssertUtils() {
}

public static String assertHtmlPage(HttpResponse<?> response, HttpStatus httpStatus) {
assertEquals(httpStatus, response.getStatus());
Optional<String> htmlOptional = response.getBody(String.class);
assertTrue(htmlOptional.isPresent());
String html = htmlOptional.get();
assertTrue(html.contains("<!DOCTYPE html>"));
return html;
public static Predicate<HttpResponse<?>> status(HttpStatus httpStatus) {
return response -> httpStatus.equals(response.getStatus());
}

public static Predicate<HttpResponse<?>> htmlBody(Predicate<String> expected) {
return response -> response.getBody(String.class)
.filter(expected)
.isPresent();
}

public static Predicate<HttpResponse<?>> htmlBody(String expected) {
return htmlBody(html -> html.contains("<!DOCTYPE html>"));
}

public static Predicate<HttpResponse<?>> htmlBody() {
return htmlBody("<!DOCTYPE html>");
}

public static Predicate<HttpResponse<?>> htmlPage(HttpStatus httpStatus) {
return status(httpStatus).and(htmlBody());
}

public static Predicate<HttpResponse<?>> htmlPage() {
return htmlPage(HttpStatus.OK);
}

public static Predicate<HttpResponse<?>> location(Predicate<String> expected) {
return response -> response.getHeaders().getFirst(HttpHeaders.LOCATION).filter(expected).isPresent();
}

public static Predicate<HttpResponse<?>> location(String location) {
return location(x -> location.equals(x));
}

public static String assertHtmlPage(HttpResponse<?> response) {
return assertHtmlPage(response, HttpStatus.OK);
public static Predicate<HttpResponse<?>> redirection(Predicate<String> expected) {
return status(HttpStatus.SEE_OTHER).and(location(expected));
}

public static void assertUnauthorized(HttpResponse<?> rsp) {
assertRedirection(rsp, s -> s.equals("/unauthorized"));
public static Predicate<HttpResponse<?>> redirection(String expected) {
return status(HttpStatus.SEE_OTHER).and(location(expected));
}

public static void assertRedirection(HttpResponse<?> rsp, Predicate<String> expected) {
assertEquals(HttpStatus.SEE_OTHER, rsp.getStatus());
String location= rsp.getHeaders().get(HttpHeaders.LOCATION);
assertNotNull(location);
assertTrue(expected.test(location));
public static Predicate<HttpResponse<?>> unauthorized() {
return redirection("/unauthorized");
}
}

Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package org.projectcheckins.http.controllers;

import static org.assertj.core.api.Assertions.assertThat;
import static org.projectcheckins.http.AssertUtils.redirection;

import io.micronaut.context.annotation.Property;
import io.micronaut.core.util.StringUtils;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.projectcheckins.http.AssertUtils;

@Property(name = "micronaut.http.client.follow-redirects", value = StringUtils.FALSE)
@Property(name = "micronaut.security.filter.enabled", value = StringUtils.FALSE)
Expand All @@ -19,7 +19,7 @@ class HomeControllerTest {
@Test
void crud(@Client("/") HttpClient httpClient) {
BlockingHttpClient client = httpClient.toBlocking();
HttpResponse<?> response = Assertions.assertDoesNotThrow(() -> client.exchange("/"));
AssertUtils.assertRedirection(response, "/question/list"::equals);
assertThat(client.exchange("/"))
.matches(redirection("/question/list"));
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package org.projectcheckins.http.controllers;

import static org.assertj.core.api.Assertions.assertThat;
import static org.projectcheckins.http.AssertUtils.htmlBody;
import static org.projectcheckins.http.AssertUtils.htmlPage;

import io.micronaut.context.annotation.Property;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.projectcheckins.http.AssertUtils;
import org.projectcheckins.http.BrowserRequest;

@Property(name = "micronaut.server.locale-resolution.fixed", value = "en")
Expand All @@ -18,8 +19,8 @@ class NotFoundControllerTest {
@Test
void crud(@Client("/") HttpClient httpClient) {
BlockingHttpClient client = httpClient.toBlocking();
HttpResponse<String> response = Assertions.assertDoesNotThrow(() -> client.exchange(BrowserRequest.GET("/notFound"), String.class));
String html = AssertUtils.assertHtmlPage(response);
Assertions.assertTrue(html.contains("Not Found"));
assertThat(client.exchange(BrowserRequest.GET("/notFound"), String.class))
.matches(htmlPage())
.matches(htmlBody("Not Found"));
}
}
Loading

0 comments on commit 4a5845f

Please sign in to comment.