如何模拟在测试中无法实例化的对象?
我在测试中使用 EasyMock 来模拟对象。但是如何模拟在代码中其他地方创建的对象呢?看下面的伪代码。我想模拟 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?
}
}
I'm using EasyMock to mock objects in my tests. But how do I mock objects that are created somewhere else in my code? Look at the following psudo code. I want to mock WebService#getPersonById, how do I do that?
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?
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
如果您使用控制反转和依赖项注入来连接服务,则模拟通常效果很好。因此,您的 Person 应该看起来像
希望很清楚,通过此更改,您现在可以为 WebService 创建一个模拟和模拟控件,然后将其插入到您的测试中,因为当您创建要测试的 Person 时,您可以传入模拟到构造函数(或设置器,如果你走那条路)。
在你的真实环境中,IoC 容器将注入真正的 Web 服务。
现在,如果你不想处理所有这些 IoC 的东西,你需要做的就是将你的 Web 服务与你的 Person 解耦(应该调用 PersonService或其他东西,而不仅仅是人,它表示实体)。换句话说,代码的编写方式你只能使用一种类型的WebService。您需要做到这一点,以便 Person 只需要某种类型的 WebService,而不是您硬编码的特定类型。
最后,在编写的代码中,WebService 是一个类,而不是一个接口。 WebService 应该是一个接口,并且您应该放入某种实现。 EasyMock 与界面配合良好;它可能能够模拟具体的类(自从我实际使用它以来已经有一段时间了),但作为设计原则,您应该指定所需的接口,而不是具体的类。
Mocking typically works well if you use inversion of control and dependency injection to wire up your services. So your person should look like
hopefully it is clear that with this change, you can now create a mock and mock control for WebService and just plug it in in your test, because when you create the Person to test, you can pass in the mock to the constructor (or setter if you go that route).
In your real environment, the IoC container will inject the real web service in.
Now, if you don't want to deal with all this IoC stuff, what you need to do is decouple your webservice from your Person (which should be call PersonService or something, not just Person, which denotes entity). In other words, the way the code is written you can only use one type of WebService. You need to make it so the Person just needs some type of WebService, not the specific one you have hard-coded in.
Finally, in the code as written, WebService is a class, not an interface. The WebService should be an interface, and you should put in some sort of implementation. EasyMock works well with interfaces; it might be able to mock concrete classes (been a while since I actually used it), but as a design principle you should specify the required interface, not the concrete class.
EasyMock(或大多数其他模拟 API)无法做到这一点。另一方面,使用 JMockit,这样的测试将非常简单和优雅:
因此,每当我们遇到一开始无法编写单元测试的情况时,我们不能自动得出被测代码不可测试的结论,并且需要重构。有时会是这样,但肯定并非总是如此。也许问题不在于被测试的代码,而在于正在使用的特定模拟工具的限制。
There is no way to do it with EasyMock (or most other mocking APIs). With JMockit, on the other hand, such a test would be very simple and elegant:
So, whenever we run into a situation where a unit test cannot be written at first, we can't automatically conclude that the code under test is untestable and needs to be refactored. Sometimes it will be the case, but certainly not always. Maybe the problem is not in the code under test, but in the limitations of a particular mocking tool that is being used.
我认为你忽略了一个更大的问题。测试的困难在于试图告诉您一些事情,即拥有一个
Person
对象(域的一部分)还使用远程服务来查找自身的更多实例(系统的一部分)是混淆的的担忧。将Person
的获取与Person
对象分开,您最终将得到更干净、更可移植的代码。不要将即时便利性(我手中有一个 Person 对象,因此我将使用它来获得更多)与可维护性混为一谈。
I think you're missing a much bigger problem. The difficulty in testing is trying to tell you something, that having a
Person
object (part of the domain) that also uses a remote service to find further instances of itself (part of the system) is mixing up concerns. Separate the getting ofPerson
s out of thePerson
object and you'll end up with cleaner, more portable code.Don't confuse immediate convenience (I have a
Person
object in my hand, so I'll use that for getting more) with maintainability.首先,您需要使
ws
成为一个模拟,通常是通过注入它。然后您可以将其放入并使用 EasyMock.expect 设置返回。
您还需要一个
PersonImpl
来拥有真正的 create 方法。First of you need to make
ws
a mock, usually by injecting it.Then you can slip it in and use EasyMock.expect to setup the return
You'll also need a
PersonImpl
to have the real create method.您可以尝试使用 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);
You can try using PowerMock whenNew method
https://github.com/jayway/powermock/wiki/MockitoUsage#how-to-mock-construction-of-new-objects
More examples here - http://www.programcreek.com/java-api-examples/index.php?api=org.powermock.api.mockito.PowerMockito
your code might look like -
whenNew(WebService.class).withAnyArguments().thenReturn(yourMockInstanceToWebServiceClass);