简体   繁体   English

类的模拟方法 <?> 用JMock输入参数

[英]Mocking Method that Takes a Class<?> Type Argument with JMock

Background: 背景:

This is a JMock+JUnit specific question (those are the two technologies I must use). 这是一个针对JMock + JUnit的问题(这是我必须使用的两种技术)。 Yes, what I want to do can be done with PowerMock, but this is an edge case that doesn't warrant changing of tools. 是的,我想做的事可以通过PowerMock来完成,但这是一个极端的情况,不能保证更换工具。 And no, sorry, I'm not asking this question to debate the philosophical validity of static methods :) 不,对不起,我不是在问这个问题来讨论静态方法的哲学有效性:)

With that out of the way, I will really thank anyone taking a look at this question. 有了这个解决方案,我真的要感谢任何关注此问题的人。

Question: 题:

I have piece of legacy code that I need to write a test for (we are trying to put tests around inherited code to ensure we don't break anything during a potentially massive refactoring effort... that's a tale for another time.) 我有一段遗留代码,我需要为其编写测试(我们正在尝试对继承的代码进行测试,以确保在潜在的大量重构工作中不会破坏任何内容……这是另外一次了。)

Goal: 目标:

The method I'm trying to mock is the Foo.bar method in the class below using JMock's class imposterizer facility (via JUnit4Mockery .) 我要模拟的方法是使用JMock的类冒名者工具(通过JUnit4Mockery )在下面的类中的Foo.bar方法。

The code below is representative to the code I'm test-wrapping: 下面的代码代表了我正在测试包装的代码:

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

My test setup aims to allow any # of calls to bar() receiving a Class instance (which obviously degenerates to Class ... silly Java type erasure "feature"), paired with any instance of Snafu. 我的测试设置旨在允许对bar()任何调用都接收一个Class实例(显然会退化为Class ...愚蠢的Java类型擦除“功能”),并与Snafu的任何实例配对。

That is the key distinction here. 这是这里的关键区别。 I'm not pairing two Matcher parameters, or two literal parameters, but one literal (T.class) and any value of type T. JMock does not allow this, so the expected solution would be to have a Matcher> and a Matcher: 我没有将两个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);
    }
});

Then, we inject the mocked Foo, which eventually gets called like this by another class, which I'll refer to as the Driver (*I'll get back to the static method call later): 然后,我们注入模拟的Foo,它最终会被另一个类这样调用,我将其称为Driver (*稍后将返回到静态方法调用):

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

Problem: 问题:

The problem is that when the Driver invokes the bar method on the mocked Foo instance, my test encounters the following exception: 问题是,当Driver在模拟的Foo实例上调用bar方法时,我的测试遇到以下异常:

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 .....

Apparently (or so it looks to me), JMock matchers' see Class instances as values, regardless of how we try to match them. 显然(在我看来,如此),无论我们尝试如何匹配,JMock匹配器都将Class实例视为值。 Or am I missing something? 还是我错过了什么?

I'm encountering similar exceptions in many legacy calls that take a java.lang.Class argument. 在许多使用java.lang.Class参数的旧式调用中,我遇到了类似的异常。 Obviously, anything that looks like X.class will be a value, not a new instance. 显然,任何看起来像X.class东西都是值,而不是新实例。

But therein lies the problem because the other argument must be resolved with a matcher, not just with an actual value. 但是这里存在问题,因为另一个参数必须使用匹配器而不是实际值来解决。


[*] Ideally one could rewrite the static method call in [*]理想情况下,可以重写静态方法调用

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

with something more amenable to mocking (a non-static method, another object or something injected with a IoC). 具有更适合模拟的东西(非静态方法,另一个对象或注入了IoC的东西)。

Probably that is the way we will eventually go, but for the time being, the code in question has a significant number of static calls. 也许这就是我们最终将要采用的方式,但是就目前而言,所讨论的代码具有大量的静态调用。

I would like to defer that till a more appropriate moment, and instead find a general JMock solution, if one exists, that allows me to set the necessary expectations of mocking functions that like Foo.bar above. 我想将其推迟到更合适的时刻,而不是找到一个通用的JMock解决方案(如果存在),该解决方案使我能够设置像上面的Foo.bar这样的Foo.bar函数的必要期望。

If I'm not mistaken, you did everything right in your test case. 如果我没记错的话,那么您在测试案例中所做的一切都是正确的。 The documentation of JMock about the with clause states JMock有关with子句的文档说明

An expectation that uses parameter matchers must use the "with" method to wrap every parameter, whether a matcher function or a literal value. 使用参数匹配器的期望值必须使用“ with”方法来包装每个参数,无论是匹配器函数还是文字值。

The important part here is the emphasize on every . 这里的重要部分是对every方面的强调。 You should only get the IllegalArgumentException you mentioned, 您应该只获得您提到的IllegalArgumentException

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 java.lang.IllegalArgumentException:并非所有参数都被赋予显式匹配器:要么必须由匹配器指定所有参数,要么必须由值指定所有参数,则不能混合匹配器和值

if you are mixing a with clause with a literal value - in your case eg 如果您将with子句与文字值混合-例如

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

where Class.class is the literal value. 其中Class.class是文字值。 See also here . 另请参阅此处

I tested your code and it seems to work as expected. 我测试了您的代码,它似乎按预期工作。 Here is my complete JUnit TestCase : 这是我完整的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();
    }
}

This test case succeeds without any runtime exceptions (tested with JUnit 4 and JMock 2.6.0). 此测试用例成功完成,没有任何运行时异常(已使用JUnit 4和JMock 2.6.0进行了测试)。 I used with(any(Class.class)) instead of with(any(Snafu.class.getClass())) for readability, but it doesn't really matter. 我使用with(any(Class.class))而不是with(any(Snafu.class.getClass()))来提高可读性,但这并不重要。

I only get the mentioned IllegalArgumentException , if I change this to 如果将其更改为,我只会得到提到的IllegalArgumentException

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

I've used this because it seems to be the only way I can get as explicit as I want to: 我之所以使用它,是因为这似乎是我可以明确表达的唯一方法:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM