Skip to content

Commit

Permalink
Make recursive comparison able to ignore specific fields
Browse files Browse the repository at this point in the history
  • Loading branch information
joel-costigliola committed Feb 13, 2019
1 parent 3691b28 commit bee3357
Show file tree
Hide file tree
Showing 10 changed files with 349 additions and 32 deletions.
13 changes: 0 additions & 13 deletions src/main/java/org/assertj/core/api/FieldLocation.java

This file was deleted.

Expand Up @@ -4,14 +4,16 @@

import java.util.List;

final class DualKey {
final class DualKey { // TODO rename DualValue

private final List<String> path;
final Object key1;
final Object key2;
final List<String> path;
final String concatenatedPath;
final Object key1; // TODO rename to actual
final Object key2; // TODO rename to expected

DualKey(List<String> path, Object key1, Object key2) {
this.path = path;
this.concatenatedPath = join(path).with(".");
this.key1 = key1;
this.key2 = key2;
}
Expand All @@ -33,16 +35,17 @@ public int hashCode() {
return h1 + h2;
}


@Override
public String toString() {
return "DualKey [key1=" + key1 + ", key2=" + key2 + "]";
return String.format("DualKey [path=%s, key1=%s, key2=%s]", concatenatedPath, key1, key2);
}

public List<String> getPath() {
return path;
}

public String getConcatenatedPath() {
return join(path).with(".");
return concatenatedPath;
}
}
Expand Up @@ -45,7 +45,7 @@ public void addLast(DualKey dualKey) {
}

private boolean shouldNotAddDualKey(DualKey dualKey) {
return recursiveComparisonConfiguration.shouldIgnoreAllActualNullFields() && dualKey.key1 == null;
return recursiveComparisonConfiguration.shouldIgnore(dualKey);
}

private boolean shouldAddDualKey(DualKey dualKey) {
Expand Down
Expand Up @@ -10,7 +10,7 @@
*
* Copyright 2012-2018 the original author or authors.
*/
package org.assertj.core.internal;
package org.assertj.core.api.recursive.comparison;

import static java.lang.String.format;
import static org.assertj.core.util.Strings.join;
Expand All @@ -23,10 +23,9 @@
import java.util.Objects;
import java.util.TreeMap;

import org.assertj.core.api.FieldLocation;
import org.assertj.core.util.VisibleForTesting;

/**
/**
* An internal holder of the comparators for fields described with {@link FieldLocation}.
*/
public class FieldComparators {
Expand Down
@@ -0,0 +1,51 @@
package org.assertj.core.api.recursive.comparison;

import java.util.Comparator;
import java.util.Objects;

// TODO should understand Map keys as field
public class FieldLocation implements Comparable<FieldLocation> {

// TODO test
private static final Comparator<FieldLocation> COMPARATOR = Comparator.comparing(FieldLocation::getFieldPath);

private String fieldPath;
// private boolean whereverInGraph = false;
// private boolean matches(Field field, Field parent); ?

@Override
public int compareTo(final FieldLocation other) {
return COMPARATOR.compare(this, other);
}

@Override
public boolean equals(final Object other) {
if (!(other instanceof FieldLocation)) return false;
FieldLocation castOther = (FieldLocation) other;
return Objects.equals(fieldPath, castOther.fieldPath);
}

@Override
public int hashCode() {
return Objects.hash(fieldPath);
}

public FieldLocation(String fieldPath) {
Objects.requireNonNull(fieldPath, "a field path can't be null");
this.fieldPath = fieldPath;
}

public String getFieldPath() {
return fieldPath;
}

@Override
public String toString() {
return String.format("FieldLocation [fieldPath=%s]", fieldPath);
}

public boolean matches(String concatenatedPath) {
return fieldPath.equals(concatenatedPath);
}

}
@@ -1,25 +1,30 @@
package org.assertj.core.api.recursive.comparison;

import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.configuration.ConfigurationProvider.CONFIGURATION_PROVIDER;
import static org.assertj.core.util.Strings.join;

import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

import org.assertj.core.annotations.Beta;
import org.assertj.core.api.FieldLocation;
import org.assertj.core.internal.FieldComparators;
import org.assertj.core.internal.TypeComparators;
import org.assertj.core.presentation.Representation;

@Beta
public class RecursiveComparisonConfiguration {

private static final String NEWLINE = format("%n");

private boolean strictTypeCheck = true;

private boolean ignoreAllActualNullFields = false;
private Set<FieldLocation> ignoredFields = new HashSet<>();
private Set<FieldLocation> ignoredFields = new LinkedHashSet<>();

private boolean ignoreCustomEquals = false;

Expand Down Expand Up @@ -57,7 +62,7 @@ public boolean shouldIgnoreAllActualNullFields() {
* Sets whether actual null fields are ignored in the recursive comparison.
* <p>
* TODO add a code example.
*
*
* @param ignoreAllActualNullFields
*/
public void setIgnoreAllActualNullFields(boolean ignoreAllActualNullFields) {
Expand All @@ -71,7 +76,41 @@ public String toString() {

public String multiLineDescription(Representation representation) {
StringBuilder description = new StringBuilder();
if (ignoreAllActualNullFields) description.append("- all actual null fields were ignored in the comparison");
if (ignoreAllActualNullFields) description.append("- all actual null fields were ignored in the comparison").append(NEWLINE);
if (!ignoredFields.isEmpty())
description.append(format("- the following fields were ignored in the comparison: %s%n", describeIgnoredFields()));
return format(description.toString());
}

private String describeIgnoredFields() {
List<String> fieldsDescription = ignoredFields.stream()
.map(FieldLocation::getFieldPath)
.collect(toList());
return join(fieldsDescription).with(", ");
}

/**
* Register the given field paths as to be ignored in the comparison.
* <p>
* TODO add a code example.
*
* @param fieldPaths the field paths to be ignored in the comparison
*/
public void ignoreFields(String... fieldPaths) {
List<FieldLocation> fieldLocations = Stream.of(fieldPaths).map(FieldLocation::new).collect(toList());
ignoredFields.addAll(fieldLocations);
}

public Set<FieldLocation> getIgnoredFields() {
return ignoredFields;
}

boolean shouldIgnore(DualKey dualKey) {
return ignoreAllActualNullFields && dualKey.key1 == null || matchesAnIgnoredField(dualKey);
}

private boolean matchesAnIgnoredField(DualKey dualKey) {
return ignoredFields.stream()
.anyMatch(fieldLocation -> fieldLocation.matches(dualKey.concatenatedPath));
}
}
@@ -0,0 +1,24 @@
package org.assertj.core.api.recursive.comparison;

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

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

public class FieldLocation_matches_Test {

@ParameterizedTest(name = "{0} matches {1}")
@CsvSource(value = {
"name, name",
"foo.bar, foo.bar",
})
public void should_match_fields(String location, String matchingFieldPath) {
// GIVEN
FieldLocation fieldLocation = new FieldLocation(location);
// WHEN
boolean match = fieldLocation.matches(matchingFieldPath);
// THEN
assertThat(match).as("%s matches %s", fieldLocation, matchingFieldPath).isTrue();
}

}
@@ -0,0 +1,93 @@
package org.assertj.core.api.recursive.comparison;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.util.Lists.list;

import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

public class RecursiveComparisonConfiguration_ignoresFields_Test {

private RecursiveComparisonConfiguration recursiveComparisonConfiguration;

@BeforeEach
public void setup() {
recursiveComparisonConfiguration = new RecursiveComparisonConfiguration();
}

@Test
public void should_register_fields_path_to_ignore_without_duplicates() {
// GIVEN
recursiveComparisonConfiguration.ignoreFields("foo", "bar", "foo.bar", "bar");
// WHEN
Set<FieldLocation> fields = recursiveComparisonConfiguration.getIgnoredFields();
// THEN
assertThat(fields).containsExactlyInAnyOrder(new FieldLocation("foo"),
new FieldLocation("bar"),
new FieldLocation("foo.bar"));
}

@ParameterizedTest(name = "{0} should be ignored")
@MethodSource("ignoringNullFieldsSource")
public void should_ignore_actual_null_fields(DualKey dualKey) {
// GIVEN
recursiveComparisonConfiguration.setIgnoreAllActualNullFields(true);
// WHEN
boolean ignored = recursiveComparisonConfiguration.shouldIgnore(dualKey);
// THEN
assertThat(ignored).as("%s should be ignored", dualKey).isTrue();
}

@SuppressWarnings("unused")
private static Stream<Arguments> ignoringNullFieldsSource() {
return Stream.of(Arguments.of(dualKey(null, "John")),
Arguments.of(dualKey(null, 123)),
Arguments.of(dualKey(null, (Object) null)),
Arguments.of(dualKey(null, new Date())));

}

@ParameterizedTest(name = "{0} should be ignored with these ignored fields {1}")
@MethodSource("ignoringSpecifiedFieldsSource")
public void should_ignore_specified_fields(DualKey dualKey, List<String> ignoredFields) {
// GIVEN
recursiveComparisonConfiguration.ignoreFields(ignoredFields.toArray(new String[0]));
// WHEN
boolean ignored = recursiveComparisonConfiguration.shouldIgnore(dualKey);
// THEN
assertThat(ignored).as("%s should be ignored with these ignored fields %s", dualKey, ignoredFields).isTrue();
}

@SuppressWarnings("unused")
private static Stream<Arguments> ignoringSpecifiedFieldsSource() {
return Stream.of(Arguments.of(randomDualKeyWithPath("name"), list("name")),
Arguments.of(randomDualKeyWithPath("name"), list("foo", "name", "foo")),
Arguments.of(randomDualKeyWithPath("name", "first"), list("name.first")),
Arguments.of(randomDualKeyWithPath("father", "name", "first"),
list("father", "name.first", "father.name.first")));

}

private static DualKey randomDualKeyWithPath(String... pathElements) {
return new DualKey(list(pathElements), new Object(), new Object());
}

private static DualKey dualKey(Object key1, Object key2) {
return new DualKey(randomPath(), key1, key2);
}

private static List<String> randomPath() {
return list(RandomStringUtils.random(RandomUtils.nextInt(0, 10)));
}

}
Expand Up @@ -18,12 +18,32 @@ public void setup() {
}

@Test
public void should_build_a_readable_multiline_description() {
public void should_show_that_null_fields_are_ignored_in_the_multiline_description() {
// WHEN
recursiveComparisonConfiguration.setIgnoreAllActualNullFields(true);
String multiLineDescription = recursiveComparisonConfiguration.multiLineDescription(STANDARD_REPRESENTATION);
// THEN
assertThat(multiLineDescription).isEqualTo("- all actual null fields were ignored in the comparison");
assertThat(multiLineDescription).isEqualTo(format("- all actual null fields were ignored in the comparison%n"));
}

@Test
public void should_show_that_some_given_fields_are_ignored_in_the_multiline_description() {
// WHEN
recursiveComparisonConfiguration.ignoreFields("foo", "bar", "foo.bar");
String multiLineDescription = recursiveComparisonConfiguration.multiLineDescription(STANDARD_REPRESENTATION);
// THEN
assertThat(multiLineDescription).isEqualTo(format("- the following fields were ignored in the comparison: foo, bar, foo.bar%n"));
}

@Test
public void should_show_a_complete_multiline_description() {
// WHEN
recursiveComparisonConfiguration.setIgnoreAllActualNullFields(true);
recursiveComparisonConfiguration.ignoreFields("foo", "bar", "foo.bar");
String multiLineDescription = recursiveComparisonConfiguration.multiLineDescription(STANDARD_REPRESENTATION);
// THEN
assertThat(multiLineDescription).isEqualTo(format("- all actual null fields were ignored in the comparison%n" +
"- the following fields were ignored in the comparison: foo, bar, foo.bar%n"));
}

@Test
Expand Down

0 comments on commit bee3357

Please sign in to comment.