如何通过 EasyMockRule 或 EasyMockRunner 委托来使用 EasyMockSupport?

How to use EasyMockSupport through delegation with EasyMockRule or EasyMockRunner?

在通过委派(而不是继承)使用EasyMockSupport时,是否可以同时利用EasyMockSupport and EasyMockRule (or EasyMockRunner)?

换句话说:如何让 EasyMockSupport 知道 EasyMockRule 创建的模拟?

这是一个 MWE,展示了我面临的问题:

// Class under test
public class MyClass {
    private Collaborator collaborator;
    public MyClass() {
        collaborator = new Collaborator();
    }

    // Method under test
    public int myMethod() {
        return collaborator.mockedMethod() + 1;
    }
}

// Class to be mocked
public class Collaborator {
    public int mockedMethod() {
        return 1;
    }
}

// Test case
public class MyClassTest {
    private EasyMockSupport easyMockSupport = new EasyMockSupport();
    @Rule public EasyMockRule easyMockRule = new EasyMockRule(this);

    @TestSubject private MyClass testSubject = new MyClass();
    @Mock private Collaborator collaboratorMock;

    @Test public void testMyMethod() {
        EasyMock.expect(collaboratorMock.mockedMethod()).andReturn(2);
        easyMockSupport.replayAll();

        int result = testSubject.myMethod();
        Assert.assertEquals("Should return 2+1 when successfully mocked", 3, result);
        // throws java.lang.AssertionError: expected: <3> but was: <1>
    }
}

测试失败,但如果 MyClassTest 扩展 EasyMockSupport,则测试通过。 (但我不能将继承用于我正在做的事情,因此我的问题。)

我对这种行为的理解是,在我的例子中,EasyMockSupport 不知道 Collaborator 模拟,所以调用 replayAll() 没有效果,模拟仍然是在 testSubject.myMethod() 中调用时处于记录状态(因此 mockedMethod() 返回 0)。 事实上,injectMocks() documentation 说:

If the parameter extends EasyMockSupport, the mocks will be created using it to allow replayAll/verifyAll to work afterwards

但是当使用委托时,参数(即测试 class)不会扩展 EasyMockSupport。有没有我遗漏或不可能的东西?

旁注:我正在使用 EasyMock 3.6。理想情况下,我想找到一个保留该版本的解决方案,但请随时指出在以后的版本中是否有相关的 feature/bugfix。

在此先感谢您的帮助!

不容易。但是,这是一个有用的用例,因此我建议您填写 issue.

同时,我们需要让规则知道 EasyMocksSupport。这是一个解决方案。

import org.easymock.EasyMock;
import org.easymock.EasyMockRule;
import org.easymock.EasyMockSupport;
import org.easymock.Mock;
import org.easymock.TestSubject;
import org.easymock.internal.MocksControl;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import java.lang.reflect.Field;

// Class under test
class MyClass {
    private Collaborator collaborator;
    public MyClass() {
        collaborator = new Collaborator();
    }

    // Method under test
    public int myMethod() {
        return collaborator.mockedMethod() + 1;
    }
}

// Class to be mocked
class Collaborator {
    public int mockedMethod() {
        return 1;
    }
}

class ExtendedEasyMockSupport extends EasyMockSupport {
    public void addMock(Object mock) {
        if(EasyMockSupport.getMockedClass(mock) == null)  {
            throw new IllegalArgumentException(mock + " is not a mock");
        }
        MocksControl control = MocksControl.getControl(mock);
        controls.add(control);
    }

    public void addAllMocks(Object testClass) {
        Field[] fields = testClass.getClass().getDeclaredFields();
        for (Field field : fields) {
            Mock annotation = field.getAnnotation(Mock.class);
            if(annotation != null) {
                field.setAccessible(true);
                Object mock;
                try {
                    mock = field.get(testClass);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
                addMock(mock);
            }
        }
    }
}

// Test case
public class MyClassTest {
    private final ExtendedEasyMockSupport easyMockSupport = new ExtendedEasyMockSupport();
    @Rule
    public EasyMockRule easyMockRule = new EasyMockRule(this);

    @TestSubject
    private final MyClass testSubject = new MyClass();

    @Mock
    private Collaborator collaboratorMock;

    @Before
    public void before() {
        easyMockSupport.addAllMocks(this);
    }

    @Test
    public void testMyMethod() {
        EasyMock.expect(collaboratorMock.mockedMethod()).andReturn(2);
        easyMockSupport.replayAll();

        int result = testSubject.myMethod();
        Assert.assertEquals("Should return 2+1 when successfully mocked", 3, result);
        // throws java.lang.AssertionError: expected: <3> but was: <1>
    }
}