Java 单元测试:测试是否调用回调的最简单方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27348625/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java unit testing: the easiest way to test if a callback is invoked
提问by Lyubomyr Shaydariv
I often work with methods that accept callbacks, and callbacks seem to be somewhat hard to test. Let's consider the following scenario, if there's a method that accepts a callback with a single method (for simplicity, I assume the testing method is synchronous), the following boilerplate could be written just to ensure that a callback method is invoked:
我经常使用接受回调的方法,而回调似乎有点难以测试。让我们考虑以下场景,如果有一个方法接受单个方法的回调(为简单起见,我假设测试方法是同步的),可以编写以下样板以确保调用回调方法:
@Test
public void testMethod() {
final boolean[] passed = {false};
method(new Callback() {
@Override
public void handle(boolean isSuccessful) {
passed[0] = isSuccessful;
}
});
assertTrue(passed[0]);
}
It looks like a surrogate. I would like to know: is there a more elegant way to test such code to make the code above look more like the pseudo-code below?
它看起来像一个代理。我想知道:有没有更优雅的方法来测试这样的代码,使上面的代码看起来更像下面的伪代码?
@Test
public void testMethod() {
// nothing explicit here, implicit boolean state provided by a test-runner
method(new Callback() {
@Override
public void handle(boolean isSuccessful) {
if ( isSuccessful ) {
pass(); // not sure how it should look like:
// * an inherited method that sets the state to "true"
// * or an object with the pass method
// * whatever
// but doesn't exit testMethod(), just sets the state
}
}
});
// nothing explicit here too:
// test runner might check if the state is changed to true
// otherwise an AssertionError might be thrown at the end of the method implicitly
}
A little cleaner. Is it possible in JUnit, TestNG or any other testing framework? Thanks!
清洁一点。是否可以在 JUnit、TestNG 或任何其他测试框架中使用?谢谢!
UPDATE
更新
Sorry, I seem to have asked a vague question that doesn't really meets what I wanted to ask. I basically meant any code (not necessarily a callback) that might be invoked if certain conditions are satisfied just to set the result state to true. Simply speaking, I just want to get rid of the initial boolean[] passed
and the final assertTrue(passed[0])
assuming that they are some kind of prologue and epilogue respectively and assuming that the initial state is set to false
so the pass()
should be invoked to set the state to true
. No matter howthe passed[0]
is set to true
, no matter where from. But unfortunately I have asked this question using the context of callbacks, however this is just an option, not a requirement. Thus the title of the question does not reflect what I really wanted to ask, but before the update some answers have been posted.
抱歉,我好像问了一个模糊的问题,并没有真正满足我想问的问题。我基本上是指在满足某些条件时可能调用的任何代码(不一定是回调),只是为了将结果状态设置为 true。简单地说,我只想摆脱初始boolean[] passed
和最终assertTrue(passed[0])
假设它们分别是某种序言和结语,并假设初始状态设置为 ,false
因此pass()
应该调用 将状态设置为true
。不管如何将passed[0]
设置为true
,无论从哪里来。但不幸的是,我已经使用回调的上下文提出了这个问题,但这只是一个选项,而不是一个要求。因此问题的标题并没有反映我真正想问的问题,但在更新之前已经发布了一些答案。
回答by fge
This is typically what a mocking framework can do for you.
这通常是模拟框架可以为您做的事情。
With Mockitofor instance:
以Mockito为例:
// imports ommited for brevity
@Test
public void callbackIsCalled()
{
final CallBack callBack = mock(CallBack.class);
method(callBack);
verify(callBack, only()).handle(any());
}
Of course, this is an example of verification mode (only()
) and value matcher (any()
). You can do more...
当然,这是验证模式 ( only()
) 和值匹配器 ( any()
)的示例。你可以做更多...
(other mocking frameworks exist, but I personally find Mockito the easiest to use, in addition to being one of the most powerful)
(存在其他模拟框架,但我个人认为 Mockito 最容易使用,而且是最强大的框架之一)
回答by Jon Skeet
Given that this is the sort of thing you're likely to need in several places, I would just create a named class to use for tests:
鉴于这是您在多个地方可能需要的那种东西,我将创建一个命名类用于测试:
public class FakeCallback implements Callback {
private boolean wasSuccessful;
private boolean handleCalled;
@Override public void handle(boolean isSuccessful) {
this.wasSuccessful = isSuccessful;
handleCalled = true;
}
// Getters for fields above
}
You can then use something like:
然后你可以使用类似的东西:
// Arrange...
FakeCallback callback = new FakeCallback();
// Act...
method(callback);
// Assert
assertTrue(callback.wasHandleCalled());
assertTrue(callback.wasSuccessful());
You could absolutely use a mocking framework for this instead, but personally I find that often it's simpler to create a single fake implementation than set up mocks repeatedly. Both ways will work though.
您绝对可以为此使用模拟框架,但我个人发现创建单个假实现通常比重复设置模拟更简单。不过这两种方式都可以。
回答by Martin Rust
Give list::add as callback
给 list::add 作为回调
When the task is to test a callback that is a functional interface accepting one parameter (here a boolean, could as well be String or any random type), it seems most concise to prepare a list, pass the List.add(e)
method as callback and then check the content of the list:
当任务是测试回调函数接口接受一个参数(这里是一个布尔值,也可以是字符串或任何随机类型)时,准备一个列表,将List.add(e)
方法作为回调传递,然后检查名单内容:
List<Boolean> callbackArgs = new ArrayList<>();
methodUnderTest(callbackArgs::add);
// assert that the callback was called exactly once and with a "true" value:
assertEquals(Arrays.asList(true), callbackArgs);
Alternative case for a callback that accepts Strings:
接受字符串的回调的另一种情况:
List<String> callbackArgs = new ArrayList<>();
methodUnderTest(callbackArgs::add);
// assert that the callback was called twice with "foo" and "bar" values respectively:
assertEquals(Arrays.asList("foo", "bar"), callbackArgs);
Analogously, a counter class may serve for testing a callback that accepts no parameter. Here using AtomicInteger, as that seems to be the only counter-like class available in the standard libs - the atomicity property is not needed here:
类似地,计数器类可以用于测试不接受参数的回调。这里使用 AtomicInteger,因为它似乎是标准库中唯一可用的类似计数器的类 - 这里不需要 atomicity 属性:
AtomicInteger callbackCounter = new AtomicInteger();
methodUnderTest(callbackCounter::incrementAndGet);
// assert that the callback was called 5 times:
assertEquals(5, callbackCounter.get());