Метод Mocking, который принимает класс <?> Аргумент типа с JMock

Фон:

Это конкретный вопрос JMock + JUnit (это две технологии я должен). Да, то, что я хочу сделать, может быть сделано с помощью PowerMock, но это крайний случай, который не требует изменения инструментов. И нет, извините, я не задаю этот вопрос для обсуждения философской действительности статических методов:)

С этой точки зрения я действительно благодарю любого, кто смотрит на этот вопрос.

Вопрос:

У меня есть часть устаревшего кода, который мне нужен для написания теста (мы пытаемся поставить тесты вокруг унаследованного кода, чтобы гарантировать, что мы не сломаем ничего во время потенциально массивного процесса рефакторинга... что сказка в другое время.)

Цель:

Метод, который я пытаюсь высмеять, - это метод Foo.bar в следующем классе, используя средство импостерирования класса JMock (через JUnit4Mockery.)

Код ниже является репрезентативным для кода, который я тестирую:

public class Foo {
     public abstract <T> void bar(
         Class<? extends T> paramClass, T paramT);

Моя тестовая установка нацелена на то, чтобы любое количество вызовов bar() получало экземпляр класса (который, очевидно, вырождается в класс... глупое стирание стирания стилей Java), в сочетании с любым экземпляром Snafu.

Это ключевое различие здесь. Я не соединяю два параметра Matcher или два литерала, но один литерал (T.class) и любое значение типа T. JMock не позволяет это, поэтому ожидаемым решением было бы иметь Matcher > и Matcher:

Foo mock = context.mock(Foo.class);
context.checking(new Expectations() {
    // keep warnings close to the culprit code when possible
    @SuppressWarnings("unchecked")
    public void allow(final Foo mockedFoo) {
        allowing(mockedFoo).bar(
            with(any(Snafu.class.getClass())), // Matcher that *should* resolve to Class<?>
            with(any(Snafu.class)));  // matcher to anything of type Snafu.class
    }
    {
        allow(mockedFoo);
    }
});

Затем мы вводим издеваемое Foo, которое в итоге получится так называемым другим классом, которое я назову как Driver (* Я вернусь к вызову статического метода позже):

// fooImpl has been replaced/injected with our mock
fooImpl.bar(Snafu.class, someStaticFunctionThatReturnsASnafu()); 

Проблема:

Проблема заключается в том, что когда Driver вызывает метод bar на посмеянный экземпляр Foo, мой тест встречается со следующим исключением:

java.lang.IllegalArgumentException: not all parameters were given explicit matchers: either all parameters must be specified by matchers or all must be specified by values, *you cannot mix matchers and values*
    at org.jmock.internal.InvocationExpectationBuilder.checkParameterMatcherCount(InvocationExpectationBuilder.java:98)
    at org.jmock.internal.InvocationExpectationBuilder.createExpectationFrom(InvocationExpectationBuilder.java:91)
    at org.jmock.internal.InvocationToExpectationTranslator.invoke(InvocationToExpectationTranslator.java:19)
    at org.jmock.internal.FakeObjectMethods.invoke(FakeObjectMethods.java:38)
    at org.jmock.lib.legacy.ClassImposteriser$4.invoke(ClassImposteriser.java:129)
    at .....

По-видимому (или так мне кажется), JMock matchers 'видят Class экземпляры как значения, независимо от того, как мы пытаемся их сопоставить. Или я что-то упускаю?

Я встречаю подобные исключения во многих унаследованных вызовах, которые принимают аргумент java.lang.Class. Очевидно, что все, что выглядит как X.class, будет значением, а не новым экземпляром.

Но в этом проблема, потому что другой аргумент должен быть разрешен с помощью сочетания, а не только с фактическим значением.


[*] В идеале можно переписать вызов статического метода в

fooImpl.bar(Snafu.class, someStaticFunctionThatReturnsASnafu()); 

с чем-то более поддающимся насмешкам (нестатический метод, другой объект или что-то введенное с помощью IoC).

Возможно, именно так мы и закончим, но пока что данный код имеет значительное количество статических вызовов.

Я хотел бы отложить это до более подходящего момента и вместо этого найти общее решение JMock, если оно существует, что позволяет мне установить необходимые ожидания насмешливых функций, подобных Foo.bar выше.

Ответ 1

Если я не ошибаюсь, вы сделали все правильно в своем тестовом примере. документация JMock о состояниях с условием

Ожидание, которое использует сопоставители параметров, должно использовать метод "с" для обертывания каждого параметра, независимо от того, является ли функция-сопряжение или литерал значение.

Важная роль здесь - подчеркнуть на every. Вы должны получить только IllegalArgumentException, о котором вы говорили,

java.lang.IllegalArgumentException: не все параметры были указаны явные совпадения: либо все параметры должны быть заданы с помощью сопоставлений или все должны быть указаны значениями, вы не можете смешивать Значения

если вы смешиваете предложение with с буквальным значением - в вашем случае, например.

allowing(mockedFoo).bar(Class.class, with(any(Snafu.class)));

где Class.class - это буквальное значение. См. Также здесь.

Я проверил ваш код и, похоже, работает так, как ожидалось. Вот мой полный JUnit TestCase:

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JUnit4Mockery;
import junit.framework.TestCase;

public class FooTest extends TestCase{
    Mockery context = new JUnit4Mockery();

    public interface Foo {
        public abstract <T> void bar(Class<? extends T> paramClass, T paramT);
    }

    public static class Snafu {}

    public void testFoo() {
        final Foo mock = context.mock(Foo.class);
        context.checking(new Expectations() {
            // keep warnings close to the culprit code when possible
            @SuppressWarnings("unchecked")
            public void allow(final Foo mockedFoo) {
                allowing(mockedFoo).bar(
                        with(any(Class.class)), // Matcher that *should* resolve to Class<?>
                        with(any(Snafu.class)));  // matcher to anything of type Snafu.class
            }
            {
                allow(mock);
            }
        });

        // test bar method (two invocations)
        mock.bar(Snafu.class, someStaticFunctionThatReturnsASnafu());
        mock.bar(Snafu.class, someStaticFunctionThatReturnsASnafu());

    }

    public static Snafu someStaticFunctionThatReturnsASnafu() {
        return new Snafu();
    }
}

Этот тестовый пример успешно выполняется без каких-либо исключений во время выполнения (тестируется с JUnit 4 и JMock 2.6.0). Я использовал with(any(Class.class)) вместо with(any(Snafu.class.getClass())) для удобства чтения, но это не имеет большого значения.

Я получаю только упомянутый IllegalArgumentException, если изменить его на

allowing(mockedFoo).bar(Class.class, with(any(Snafu.class)));

Ответ 2

Я использовал это, потому что это единственный способ получить явное, как я хочу:

allowing(mockedFoo).bar(
    with(Expectations.<Class<Snafu>>anything()),
    with(any(Snafu.class))
);