You should be able to mock protocols using any mock library. Under the covers, every protocol uses a Java interface as an implementation detail, and you could just mock that interface.
That said, don't do this! Mocking in Java is absurdly complex because of reflection, protection levels, final classes, etc. Any time you want a Clojure object that implements a protocol, simply call reify, e.g.
(fact "you can test in terms of a record's methods"
(let [obj (->SideEffectThing :fake-connection)]
(user-of-side-effect-thing obj) => 0
(provided
(read obj) => -1)
)
)
interface ISideEffect { int read(); void write(Object something); }
class SideEffectThing implements ISideEffect { ... }
// in test sources:
public class SomeOtherComponentSpec {
private ISideEffect obj;
@Before
public void setup() { obj = mock(ISideEffect.class); }
@Test
public void does_something_useful() {
when(obj.read()).thenReturn(-1);
OtherComponent comp = new OtherComponent(obj);
int actual = comp.doSomethingUseful();
assertEquals(0, actual);
verify(obj).read();
}
It looks like the more recent versions of Midje provide this functionality quite nicely.
First, I would like to note that this kind of mocking is very commonly useful when splitting larger programs into components (e.g. that are assembled via dependency injection with libraries like Stuart Sierra's component library). If I've got some component that isolates a set of side-effect functions into a conceptual component I certainly want a testing framework that will let me inject a stand-in component so that I can:
Write code that uses the component before it even exists (top-down).
Test functions that use that component in isolation from the real implementation of the component.
You could use Mockito or some other library, but I agree such a solution is not going to be particularly elegant.
Unfortunately, protocols and records generate classes that Midje cannot hack into as easily as functions...so you do have to modify your code slightly:
By defining your component with defrecord-openly, you gain the ability to specify the behavior of the component's methods using Midje's "provided" mechanism:
(fact "you can test in terms of a record's methods"
(let [obj (->SideEffectThing :fake-connection)]
(user-of-side-effect-thing obj) => 0
(provided
(read obj) => -1)
)
)
You could, of course, avoid relying on a production type here (which I would advocate), and also avoid peppering defrecord-openly throughout your production code. In this case, just move SideEffectThing (as written above) into your test code. Then your component system in the application can plug in the real component, but your tests can be written against this unimplemented test version.
To be complete, I will compare the equivalent Java Mockito code with the above solution. In Java:
interface ISideEffect { int read(); void write(Object something); }
class SideEffectThing implements ISideEffect { ... }
// in test sources:
public class SomeOtherComponentSpec {
private ISideEffect obj;
@Before
public void setup() { obj = mock(ISideEffect.class); }
@Test
public void does_something_useful() {
when(obj.read()).thenReturn(-1);
OtherComponent comp = new OtherComponent(obj);
int actual = comp.doSomethingUseful();
assertEquals(0, actual);
verify(obj).read();
}
this Java solution mocks out the component, specifies required behavior of that component, and then not only checks that the component itself works properly, but also that the component depended on the call to read() in some way. Mockito also supports pattern matching on arguments (and capture) to analyze how the component was used, if needed.
The Midje example above does much of that, and in a much clearer form. If you indicate (in the provided clause) that the function is called with specific arguments, then the test will fail if it isn't. If you specify that the function is called more than once (and it isn't), then that is a failure. For example, to indicate the read will be called 3 times and should return different values:
(fact "you can test in terms of a record's methods"
(let [obj (->SideEffectThing :fake-connection)]
(user-of-side-effect-thing obj) => 0
(provided
(read obj) => -1
(read obj) => 6
(read obj) => 99
)
)
)
indicates that you expect read to be called three times, and that it should return the specified sequence of values. See the docs on prerequisites for more details, including how to specify exact number of times on a single function spec in provided, and how to indicate the function is never called.
发布评论
评论(2)
您应该能够使用任何模拟库来模拟协议。在幕后,每个协议都使用 Java 接口作为实现细节,您可以模拟该接口。
也就是说,不要这样做!由于反射、保护级别、最终类等原因,Java 中的模拟异常复杂。任何时候您想要一个实现协议的 Clojure 对象,只需调用 reify,例如 请
注意,您无需存根不打算调用的方法。
You should be able to mock protocols using any mock library. Under the covers, every protocol uses a Java interface as an implementation detail, and you could just mock that interface.
That said, don't do this! Mocking in Java is absurdly complex because of reflection, protection levels, final classes, etc. Any time you want a Clojure object that implements a protocol, simply call reify, e.g.
Note that you need not stub the methods you don't plan to call.
看起来最新版本的 Midje 很好地提供了此功能。
首先,我想指出,当将较大的程序拆分为组件时(例如,通过依赖注入与 Stuart Sierra 的 组件库)。如果我有一些组件将一组副作用函数隔离到一个概念组件中,我当然需要一个测试框架,它可以让我注入一个替代组件,这样我就可以:
您可以使用 Mockito 或其他一些库,但我同意这样的解决方案不会特别优雅。
不幸的是,协议和记录生成 Midje 无法像函数一样轻松破解的类...因此您必须稍微修改代码:
请参阅 Midje 关于生产模式的文档,详细了解如何使此修改不影响您的生产运行时。
通过使用 defrecord-openly 定义组件,您可以指定使用 Midje 的“提供”机制的组件方法的行为:
当然,您可以避免在这里依赖生产类型(我主张这样做),并且还可以避免在整个生产代码中公开地添加 defrecord。在这种情况下,只需将 SideEffectThing(如上所述)移至测试代码中即可。然后,应用程序中的组件系统可以插入真正的组件,但您的测试可以针对这个未实现的测试版本编写。
为了完整起见,我将把等效的 Java Mockito 代码与上述解决方案进行比较。在 Java 中:
此 Java 解决方案模拟组件,指定该组件所需的行为,然后不仅检查组件本身是否正常工作,而且还检查组件以某种方式依赖于对 read() 的调用。 Mockito 还支持参数(和捕获)的模式匹配,以分析组件的使用方式(如果需要)。
上面的 Midje 示例完成了大部分工作,并且形式更加清晰。如果您(在提供的子句中)指示使用特定参数调用该函数,则如果不是,则测试将失败。如果您指定该函数被多次调用(但事实并非如此),那么就会失败。例如,指示 read 将被调用 3 次并且应该返回不同的值:
表示您希望 read 被调用 3 次,并且它应该返回指定的值序列。请参阅有关先决条件的文档了解更多详细信息,包括如何指定提供的单个函数规范的确切次数,以及如何指示该函数从未被调用。
It looks like the more recent versions of Midje provide this functionality quite nicely.
First, I would like to note that this kind of mocking is very commonly useful when splitting larger programs into components (e.g. that are assembled via dependency injection with libraries like Stuart Sierra's component library). If I've got some component that isolates a set of side-effect functions into a conceptual component I certainly want a testing framework that will let me inject a stand-in component so that I can:
You could use Mockito or some other library, but I agree such a solution is not going to be particularly elegant.
Unfortunately, protocols and records generate classes that Midje cannot hack into as easily as functions...so you do have to modify your code slightly:
See Midje's documentation on production mode for details about how to make this modification not affect your production runtime.
By defining your component with defrecord-openly, you gain the ability to specify the behavior of the component's methods using Midje's "provided" mechanism:
You could, of course, avoid relying on a production type here (which I would advocate), and also avoid peppering defrecord-openly throughout your production code. In this case, just move SideEffectThing (as written above) into your test code. Then your component system in the application can plug in the real component, but your tests can be written against this unimplemented test version.
To be complete, I will compare the equivalent Java Mockito code with the above solution. In Java:
this Java solution mocks out the component, specifies required behavior of that component, and then not only checks that the component itself works properly, but also that the component depended on the call to read() in some way. Mockito also supports pattern matching on arguments (and capture) to analyze how the component was used, if needed.
The Midje example above does much of that, and in a much clearer form. If you indicate (in the provided clause) that the function is called with specific arguments, then the test will fail if it isn't. If you specify that the function is called more than once (and it isn't), then that is a failure. For example, to indicate the read will be called 3 times and should return different values:
indicates that you expect read to be called three times, and that it should return the specified sequence of values. See the docs on prerequisites for more details, including how to specify exact number of times on a single function spec in provided, and how to indicate the function is never called.