Skip to content

Instantly share code, notes, and snippets.

@jbrains
Created October 13, 2018 19:16
Show Gist options
  • Save jbrains/a44701162b41c423e312792f94eee37e to your computer and use it in GitHub Desktop.
Save jbrains/a44701162b41c423e312792f94eee37e to your computer and use it in GitHub Desktop.
An example of using a custom Hamcrest matcher to write tests. What do you think of this style?
package ca.jbrains.java.test;
import ca.jbrains.java.ReaderBasedTextSource;
import io.vavr.collection.List;
import io.vavr.collection.Stream;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Assert;
import org.junit.Test;
import java.io.StringReader;
import static ca.jbrains.java.test.LearnReadingLinesTest.TextParsesIntoLines.parsesInto;
public class LearnReadingLinesTest {
@Test
public void readOneLineWithoutALineSeparator() throws Exception {
Assert.assertThat(
"this is one line of text without a line separator.",
parsesInto(List.of(
"this is one line of text without a line separator.")));
}
@Test
public void readOneLineFromABlobOfTextEndingWithALineSeparator() throws Exception {
Assert.assertThat(
endWithLineSeparator("this is one line of text ending in a line separator."),
parsesInto(List.of(
"this is one line of text ending in a line separator.")));
}
@Test
public void readMultipleLinesEndingWithSeveralEmptyLines() throws Exception {
Assert.assertThat(new StringBuilder()
.append(endWithLineSeparator("a non-empty line of text"))
.append(endWithLineSeparator("a non-empty line of text"))
.append(endWithLineSeparator("a non-empty line of text"))
.append(endWithLineSeparator(""))
.append(endWithLineSeparator(""))
.append(endWithLineSeparator(""))
.append(endWithLineSeparator(""))
.toString(),
parsesInto(List.of(
"a non-empty line of text",
"a non-empty line of text",
"a non-empty line of text",
"",
"",
"",
"")));
}
@Test
public void readNothing() throws Exception {
Assert.assertThat("", parsesInto(List.empty()));
}
@Test
public void readOnlyOneEmptyLine() throws Exception {
Assert.assertThat(System.lineSeparator(), parsesInto(List.of("")));
}
// REFACTOR Maybe move to a generic text library?
public static String endWithLineSeparator(String text) {
return String.format("%s%s", text, System.lineSeparator());
}
/**
* Matches multiline text as lines, where lines are separated
* by {@code System.lineSeparator()}.
*/
public static class TextParsesIntoLines extends TypeSafeMatcher<String> {
private List<String> expectedLines;
public TextParsesIntoLines(List<String> expectedLines) {
this.expectedLines = expectedLines;
}
@Override
protected boolean matchesSafely(String text) {
return tokenizeAsLines(text).equals(expectedLines);
}
private List<String> tokenizeAsLines(String text) {
ReaderBasedTextSource textSource = new ReaderBasedTextSource(new StringReader(text));
Stream<String> streamOfLines = textSource.parseIntoLines();
return List.ofAll(streamOfLines);
}
@Override
protected void describeMismatchSafely(String text, Description mismatchDescription) {
mismatchDescription.appendText("instead parses into lines ")
.appendValueList("[", ",", "]", tokenizeAsLines(text));
}
@Override
public void describeTo(Description description) {
description.appendText("parses into lines ")
.appendValueList("[", ",", "]", expectedLines);
}
@Factory
public static Matcher<String> parsesInto(List<String> expectedLines) {
return new TextParsesIntoLines(expectedLines);
}
}
}
@jbrains
Copy link
Author

jbrains commented Oct 14, 2018

@dhemery @rubberduck203 I find one aspect of ReaderBasedTextSource that I believe doesn't fit the test: parseIntoLines() returns a Stream, but we can't compare Streams directly for equality, as far as I know, so I want Lists. Now we end up combining two bits of behavior in the matcher: invoking the production code and adapting the output to something test-friendly. I would prefer to separate those things.

I suppose I could compose the matcher with something that runs the production code in order to call that out better. I might play around with that a little.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment