Estoy tratando de usar los igualadores de hamcrest para hacer coincidir una lista de objetos con una lista / matriz de sus propiedades. Para un valor de propiedad, esto no es un problema, porque puedo hacer algo como esto:

assertThat(savedGroup.getMembers(),
    containsInAnyOrder(hasProperty("name", is(NAMES[0]))));

Para múltiples valores de propiedad, puedo usar múltiples llamadas hasProperty ()

assertThat(savedGroup.getMembers(),
    containsInAnyOrder(
        hasProperty("name", is(NAMES[0])),
        hasProperty("name", is(NAMES[1]))));

Pero, ¿existe una forma genérica de hacer coincidir todos los valores en la matriz NAMES?

1
Nitek 26 ene. 2016 a las 12:20

3 respuestas

La mejor respuesta

La mejor manera (IMO) de hacer esto sería combinar el Matcher containsInAnyOrder sobrecargado junto con un FeatureMatcher personalizado. En última instancia, su código se vería así:

String[] expectedNames = new String[] { "John", "Bob", "Carol"};
assertThat(savedGroup.getMembers(), hasNames(expectedNames));

hasNames se implementa de la siguiente manera:

private Matcher<Iterable<? extends Member>> hasNames(String[] expectedNames) {
    return containsInAnyOrder(Arrays.stream(expectedNames).map(name -> name(name)).collect(Collectors.toList()));
}

Y la parte final es la llamada a name que genera un Matcher que extraerá una propiedad de forma segura para los tipos de su objeto:

private Matcher<Member> name(String name) {
    return new FeatureMatcher<Member, String>(equalTo(name), "name", "name") {
        @Override
        protected String featureValueOf(Member actual) {
            return actual.getName();
        }
    };
}

El beneficio de hacerlo de esta manera es que:

  • Obtiene el beneficio de la seguridad de tipos en lugar de usar hasProperty
  • Su prueba ahora describe con qué desea hacer coincidir, es decir, hasNames
  • El código producido ahora es más flexible y componible. ¿Quiere hacer coincidir el nombre de un solo objeto? Todo lo que necesita hacer ahora es assertThat(member, has(name("Fred")))

Puede obtener aún más capacidad de composición moviendo el subcomparador equalTo para que sea parte de la llamada hasNames de esta manera:

private Matcher<Iterable<? extends Member>> hasNames(String[] expectedNames) {
    return containsInAnyOrder(Arrays.stream(expectedNames).map(name -> name(equalTo(name))).collect(Collectors.toList()));
}

private Matcher<Member> name(Matcher<String> nameMatcher) {
    return new FeatureMatcher<Member, String>(nameMatcher, "name", "name") {
        @Override
        protected String featureValueOf(Member actual) {
            return actual.getName();
        }
    };
}
3
tddmonkey 26 ene. 2016 a las 22:16

Necesito hacer una limpieza (salida de descripción), pero creo que resuelve su problema:

package org.example.matchers;

import java.util.List;

import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.TypeSafeMatcher;

public class ContainsArrayElementsInAnyOrder<T> extends TypeSafeMatcher<List<T>> {

    private T[] toMatch;

    public ContainsArrayElementsInAnyOrder(final T[] toMatch) {
        this.toMatch = toMatch;
    }

    @Override
    protected boolean matchesSafely(List<T> item) {
        if(item.size() != toMatch.length) {
            return false;
        }
        for (T t : toMatch) {
            if(!item.contains(t)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void describeMismatchSafely(List<T> item, Description mismatchDescription) {
        mismatchDescription.appendValueList("[", ",", "]", item);
    }

    @Override
    public void describeTo(Description description) {
        description.appendValueList("[", ",", "]", toMatch);
    }

    @Factory
    public static <T> ContainsArrayElementsInAnyOrder<T> containsArrayElementsInAnyOrder(T[] elements) {
        return new ContainsArrayElementsInAnyOrder<T>(elements);
    }    


}

Prueba:

@Test
public void shouldContainsInAnyOrderSameElementsInArrayAsInList() {
    final String[] NAME = new String[]{"name3", "name1", "name2"};

    final List<String> result = new ArrayList<>(3);
    result.add("name2");
    result.add("name1");
    result.add("name4");

    assertThat(result, containsArrayElementsInAnyOrder(NAME));
}

Salida si no coincide:

java.lang.AssertionError: 
Expected: ["name3","name1","name2"]
     but: ["name2","name1","name4"]
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
    at org.junit.Assert.assertThat(Assert.java:956)
    at org.junit.Assert.assertThat(Assert.java:923)
    at ..
0
bodo 27 ene. 2016 a las 08:14

Una de las sobrecargas de containsInAnyOrder acepta una colección de comparadores como argumento. Por lo tanto, podría hacer algo como esto:

assertThat(
    savedGroup.getMembers(),
    containsInAnyOrder(
        Stream.of(NAMES)
              .map(name -> hasProperty("name", is(name)))
              .collect(Collectors.toList()) 
    ));

(si usa Java 8, de lo contrario, necesitaría agregar un bucle para construir la colección)

1
blgt 26 ene. 2016 a las 15:08