Verifique el valor del atributo del objeto con mockito

264

Tengo una llamada al método que quiero burlarme de mockito. Para empezar, he creado e inyectado una instancia de un objeto en el que se llamará al método. Mi objetivo es verificar uno de los objetos en la llamada al método.

¿Hay alguna forma en que mockito le permita afirmar o verificar el objeto y sus atributos cuando se llama al método simulado?

ejemplo

Mockito.verify(mockedObject)
       .someMethodOnMockedObject(
              Mockito.<SomeObjectAsArgument>anyObject())

En lugar de hacerlo, anyObject()quiero verificar que el objeto de argumento contenga algunos campos particulares

Mockito.verify(mockedObject)
       .someMethodOnMockedObject(
              Mockito.<SomeObjectAsArgument>**compareWithThisObject()**)
Priyank
fuente
Como alternativa al uso de mockito en estos casos, puede considerar crear un código auxiliar personalizado que amplíe la clase de mockedObject y anule someMethodOnMockedObject para guardar el objeto para una comparación posterior.
Gonen I

Respuestas:

540

La nueva función agregada a Mockito lo hace aún más fácil,

ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
verify(mock).doSomething(argument.capture());
assertEquals("John", argument.getValue().getName());

Echa un vistazo a la documentación de Mockito


En caso de que haya más de un parámetro y se desee capturar solo un parámetro, use otros ArgumentMatchers para ajustar el resto de los argumentos:

verify(mock).doSomething(eq(someValue), eq(someOtherValue), argument.capture());
assertEquals("John", argument.getValue().getName());
iraSenthil
fuente
1
Si su método tiene más de un argumento, también debe usar Matchers para todos los demás argumentos. akcasoy.wordpress.com/tag/argumentcaptor
robsonrosa
1
¿Qué pasa si hay múltiples argumentos? ¿Cómo especificas el que te interesa exactamente?
IgorGanapolsky
2
@IgorGanapolsky Suponiendo un segundo parámetro de cadena para doSomething que necesita hacer: verificar (simulacro) .doSomething (argumento.capture (), anyString ());
GreenTurtle
la necesidad de usar matchers para todos los argumentos es únicamente según la especificación de uso de matcher all-or-none.
Charney Kaye
54

Creo que la forma más fácil de verificar un objeto de argumento es usar el refEqmétodo:

Mockito.verify(mockedObject).someMethodOnMockedObject(Matchers.refEq(objectToCompareWith));

Se puede usar incluso si el objeto no se implementa equals(), porque se usa la reflexión. Si no desea comparar algunos campos, simplemente agregue sus nombres como argumentos para refEq.

John29
fuente
1
esa es una manera muy elegante pero desafortunadamente org.mockito.Matchers ahora está en desuso
ihebiheb
55
@ihebiheb Se trasladó a ArgumentMatchers
Michael
48

Una posibilidad más, si no desea usar ArgumentCaptor(por ejemplo, porque también está usando stubbing), es usar Hamcrest Matchers en combinación con Mockito.

import org.mockito.Mockito
import org.hamcrest.Matchers
...

Mockito.verify(mockedObject).someMethodOnMockedObject(MockitoHamcrest.argThat(
    Matchers.<SomeObjectAsArgument>hasProperty("propertyName", desiredValue)));
charleyc
fuente
2
Nota al margen: asegúrese de que el Matcherspaquete sea correcto, ya que escribir la misma línea de código con la org.mockito.Matchersclase arroja una excepción engañosa que indica que el parámetro de la función simulada simplemente no coincide.
Buer
1
Tenga en cuenta que en las versiones modernas de Mockito, es MockitoHamcrest.argThat()y noMockito.argThat()
Roman Puchkovskiy
17

Esta es una respuesta basada en la respuesta de iraSenthil pero con anotación ( Captor ). En mi opinión tiene algunas ventajas:

  • es mas corto
  • es mas facil de leer
  • puede manejar genéricos sin advertencias

Ejemplo:

@RunWith(MockitoJUnitRunner.class)
public class SomeTest{

    @Captor
    private ArgumentCaptor<List<SomeType>> captor;

    //...

    @Test 
    public void shouldTestArgsVals() {
        //...
        verify(mockedObject).someMethodOnMockedObject(captor.capture());

        assertThat(captor.getValue().getXXX(), is("expected"));
    }
}
Walery Strauch
fuente
Esto solo funcionará para un único argumento en params.
IgorGanapolsky
Puede usar un captor para más de un argumento. Si captura más de un argumento, puede enumerar todos los resultados con captor.getAllValues(). El método captor.getValue()que se utiliza en la respuesta ofrece el último resultado.
Walery Strauch
11

Si usa Java 8, puede usar expresiones Lambda para que coincidan.

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

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;

public class LambdaMatcher<T> extends BaseMatcher<T>
{
    private final Predicate<T> matcher;
    private final Optional<String> description;

    public LambdaMatcher(Predicate<T> matcher)
    {
        this(matcher, null);
    }

    public LambdaMatcher(Predicate<T> matcher, String description)
    {
        this.matcher = matcher;
        this.description = Optional.ofNullable(description);
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean matches(Object argument)
    {
        return matcher.test((T) argument);
    }

    @Override
    public void describeTo(Description description)
    {
        this.description.ifPresent(description::appendText);
    }
}

Llamada de ejemplo

@Test
public void canFindEmployee()
{
    Employee employee = new Employee("John");
    company.addEmployee(employee);

    verify(mockedDal).registerEmployee(argThat(new LambdaMatcher<>(e -> e.getName()
                                                                         .equals(employee.getName()))));
}

Más información: http://source.coveo.com/2014/10/01/java8-mockito/

GuiSim
fuente
5

Las soluciones anteriores realmente no funcionaron en mi caso. No pude usar ArgumentCaptor ya que el método fue llamado varias veces y necesitaba validar cada uno. Un simple Matcher con "argThat" hizo el truco fácilmente.

Matcher personalizado

// custom matcher
private class PolygonMatcher extends ArgumentMatcher<PolygonOptions> {
    private int fillColor;
    public PolygonMatcher(int fillColor) {
        this.fillColor = fillColor;
    }

    @Override
    public boolean matches(Object argument) {
        if (!(argument instanceof PolygonOptions)) return false;
        PolygonOptions arg = (PolygonOptions)argument;
        return Color.red(arg.getFillColor()) == Color.red(fillColor)
                && Color.green(arg.getFillColor()) == Color.green(fillColor)
                && Color.blue(arg.getFillColor()) == Color.blue(fillColor);
    }
}

Test Runner

// do setup work setup
// 3 light green polygons
int green = getContext().getResources().getColor(R.color.dmb_rx_bucket1);
verify(map, times(3)).addPolygon(argThat(new PolygonMatcher(green)));

// 1 medium yellow polygons
int yellow = getContext().getResources().getColor(R.color.dmb_rx_bucket4);
    verify(map, times(1)).addPolygon(argThat(new PolygonMatcher(yellow)));

// 3 red polygons
int orange = getContext().getResources().getColor(R.color.dmb_rx_bucket5);
verify(map, times(3)).addPolygon(argThat(new PolygonMatcher(orange)));

// 2 red polygons
int red = getContext().getResources().getColor(R.color.dmb_rx_bucket7);
verify(map, times(2)).addPolygon(argThat(new PolygonMatcher(red)));
silbido
fuente
3

Y una solución muy agradable y limpia en koltin de com.nhaarman.mockito_kotlin

verify(mock).execute(argThat {
    this.param = expected
})
Cililing
fuente
1

Puedes referir lo siguiente:

Mockito.verify(mockedObject).someMethodOnMockedObject(eq(desiredObject))

Esto verificará si el método de mockedObject se llama con el objeto deseado como parámetro.

zaid bepari
fuente
1

Otra forma fácil de hacerlo:

import org.mockito.BDDMockito;    
import static org.mockito.Matchers.argThat;
import org.mockito.ArgumentMatcher;

BDDMockito.verify(mockedObject)
        .someMethodOnMockedObject(argThat(new ArgumentMatcher<TypeOfMethodArg>() {

            @Override
            public boolean matches(Object argument) {
                final TypeOfMethodArg castedArg = (TypeOfMethodArg) argument;

                // Make your verifications and return a boolean to say if it matches or not
                boolean isArgMarching = true;

                return isArgMarching;
            }
        }));
pierrefevrier
fuente
0

¡El javadoc para refEq mencionó que el control de igualdad es superficial! Puede encontrar más detalles en el siguiente enlace:

[ https://static.javadoc.io/org.mockito/mockito-core/2.2.29/org/mockito/ArgumentMatchers.html#refEq(T,%20java.lang.String...)font>[1]

El problema de "igualdad superficial" no se puede controlar cuando utiliza otras clases que no implementan el método .equals (), la clase "DefaultMongoTypeMapper" es un ejemplo en el que no se implementa el método .equals ().

org.springframework.beans.factory.support ofrece un método que puede generar una definición de bean en lugar de crear una instancia del objeto, y se puede usar para eliminar el error de comparación.

 genericBeanDefinition(DefaultMongoTypeMapper.class)
                        .setScope(SCOPE_SINGLETON)
                        .setAutowireMode(AUTOWIRE_CONSTRUCTOR)
                        .setLazyInit(false)
                        .addConstructorArgValue(null)
                        .getBeanDefinition()

** "La definición del bean es solo una descripción del bean, no un bean en sí. Las descripciones del bean implementan correctamente equals () y hashCode (), por lo que en lugar de crear un nuevo DefaultMongoTypeMapper () proporcionamos una definición que le dice a Spring cómo funciona. debería crear uno "

En tu ejemplo, puedes hacer algo así

Mockito.verify(mockedObject)
       .doSoething(genericBeanDefinition(YourClass.class).setA("a")
       .getBeanDefinition());
Saif Masadeh
fuente
0

Una solución simplificada, sin crear una nueva clase de implementación de Matcher y usar la expresión lambda:

        verify(mockObject).someMockMethod(argThat((SomeArgument arg) -> arg.fieldToMatch.equals(expectedFieldValue));
murali mohan
fuente