如何模拟我无法在测试中实例化的对象?

时间:2011-01-26 16:47:26

标签: java mocking easymock

我正在使用EasyMock在我的测试中模拟对象。但是,我如何模拟在我的代码中的其他位置创建的对象?查看以下psudo代码。我想模拟WebService#getPersonById,我该怎么做?

public class Person {
  public Person find(int id) {
    WebService ws = new WebService();
    return ws.getPersonById(id);
  }
}

public class PersonTest {
  testFind() {
    // How do I mock WebService#getPersonById here?
  }
}

6 个答案:

答案 0 :(得分:9)

如果使用控制和依赖注入的反转来连接服务,模拟通常很有效。所以你的人应该看起来像

public class Person() {
  WebService ws = null;

  // or use setters instead of constructor injection
  Persion(WebService ws) {
     this.ws = ws;
  }
  public Person find(int id) {
    return ws.getPersonById(id);
  }
}

希望很明显,通过此更改,您现在可以为WebService创建模拟和模拟控件,并将其插入到测试中,因为当您创建要测试的Person时,您可以将模拟传递给构造函数(或者如果你去那条路线,那就是设定者)。

在您的真实环境中,IoC容器将注入真实的Web服务。

现在,如果你不想处理所有这些IoC的东西,你需要做的就是将你的web服务与你的Person(应该是PersonService或者其他东西,而不仅仅是Person,它表示实体)分离。换句话说,编写代码的方式只能使用一种类型的WebService。您需要创建它,以便Person只需要某些类型的WebService,而不是您硬编码的特定类型。

最后,在编写的代码中,WebService是一个类,而不是一个接口。 WebService应该是一个接口,您应该进行某种实现。 EasyMock适用于界面;它可能能够模拟具体的类(自从我实际使用它以来已经有一段时间了),但作为一个设计原则,你应该指定所需的接口,而不是具体的类。

答案 1 :(得分:3)

使用EasyMock(或大多数其他模拟API)无法做到这一点。另一方面,使用JMockit,这样的测试将非常简单和优雅:

public class PersonTest
{
    @Test
    public testFind(@Mocked final WebService ws) {
        final int id = 123;

        new NonStrictExpectations() {{
            ws.getPersonById(id); result = new Person(id);
        }};

        Person personFound = new Person().find(id);

        assertEquals(id, personFound.getId());
    }
}

因此,每当我们遇到无法首先编写单元测试的情况时,我们就不能自动断定测试中的代码是不可测试的并且需要重构。有时会出现这种情况,但肯定并非总是如此。也许问题不在于测试中的代码,而在于正在使用的特定模拟工具的局限性。

答案 2 :(得分:1)

我认为你错过了一个更大的问题。测试的难点在于试图告诉你一些事情,即拥有Person对象(域的一部分)也使用远程服务来查找自身的其他实例(系统的一部分)正在混淆问题。将Person对象从Person对象中分离出来,最终会得到更干净,更便携的代码。

不要混淆立即的便利(我手上有一个Person对象,所以我会用它来获得更多)具有可维护性。

答案 3 :(得分:0)

首先,您需要将ws作为模拟,通常是通过注入它。

public abstract class Person() {
  public Person find(int id) {
    WebService ws = createWebService();
    return ws.getPersonById(id);
  }
  protected abstract WebService createWebService();
}

然后您可以将其放入并使用EasyMock.expect设置返回

public class PersonTest() {
  testFind() {
    WebService mock = EasyMock.createMock(WebService.class);
    Person p = new Persion() {
      protected WebService createWebService() {
        return mock;
      }
    }
    EasyMock.expect(mock.getPersonById()).andReturn(dummyValue);
    //Test code
  }
}

您还需要PersonImpl才能拥有真正的创建方法。

答案 4 :(得分:0)

我最终使用了JMockit。它支持模拟一个类的所有实例。

答案 5 :(得分:0)

您可以尝试使用PowerMock whenNew方法

https://github.com/jayway/powermock/wiki/MockitoUsage#how-to-mock-construction-of-new-objects

此处有更多示例 - http://www.programcreek.com/java-api-examples/index.php?api=org.powermock.api.mockito.PowerMockito

您的代码可能看起来像 -

whenNew(WebService.class).withAnyArguments().thenReturn(yourMockInstanceToWebServiceClass);