我有一个控制器操作方法,无论发生什么,它总是返回 null,因为我希望它重新加载同一页面。(JSF 2.2)。
我已经成功地使用mockito来协调每条可能的路径的执行。
但是,每个路径大多调用抽象方法来添加消息或调用第三方库。
所以我可以断言它返回 null,但无论如何都是如此。我看到这种模式随着开发的继续而重复。
问题:尽管执行路径,我总是在测试 null。
我所看到的可能的解决方案:
- 我对嘲笑很绿,所以我可能还有其他事情可以做以验证是否调用了第三方和抽象方法。
-
我觉得可能有用但黑客的东西是状态标志某种方式可以知道刚刚添加到堆栈中的消息类型。Hacky,因为它唯一真正的用途是它将用于测试,正如我目前所看到的那样。
-
重新评估我的方法,如果我处于这种情况,因为我代码设计是错误的。
- 我没有问题。 保持原样,并确信我正在运行每个执行路径并验证其结果,即使认为它是相同的。
问题:给定已知内容,您将首先考虑哪个方向来尝试解决在单元测试中从外部验证内部执行路径的问题? 还是有更好的解决方案?
提前谢谢。
使用示例代码更新以解释验证问题,如果这是我应该采取的路线:
try {
account.save(); //<-- third party object i don't own, & returns void
addInfoMessage("All Updated!"); //<-- abstract method
} catch (final ResourceException e) { //<-- third party exception
addErrorMessage("Sorry your account could not be updated. ");//<-- abstract method
LOG.error("error msg");
}
...
return null;
看起来至少有两个结果,所以覆盖它们很重要。您可以稍微重新设计类以使测试变得容易。
public abstract class YourClass {
protected abstract void addInfoMessage(String message);
protected abstract void addErrorMessage(String message);
public void closeTransaction() {
try {
saveAccountInternal(); /* ! */
addInfoMessage("All Updated!");
} catch (final ResourceException e) {
addErrorMessage("Sorry your account could not be updated.");
}
return null;
}
/** Package-private. Overridable for testing. */
void saveAccountInternal() throws ResourceException {
account.save();
}
}
您正在设计一个用于子类化的类,因此请使用子类对其进行测试:
public class YourClassTest {
private static class TestYourClass extends YourClass {
boolean saveCalled = false;
boolean shouldThrow = false;
List<String> infoMessages = new ArrayList<>();
List<String> errorMessages = new ArrayList<>();
protected void addInfoMessage(String message) { infoMessages.add(message); }
protected void addErrorMessage(String message) { errorMessages.add(message); }
@Override void saveAccountInternal() throws ResourceException {
saveCalled = true;
if (shouldThrow) throw new ResourceException();
}
}
@Test public void closeTransactionShouldSave() {
TestYourClass testYourClass = new TestYourClass();
assertNull(testYourClass.closeTransaction());
assertTrue(testYourClass.saveCalled);
assertEquals(1, testYourClass.infoMessages.size());
assertEquals(0, testYourClass.errorMessages.size());
}
@Test public void closeTransactionShouldSave() {
TestYourClass testYourClass = new TestYourClass();
testYourClass.shouldThrow = true;
assertNull(testYourClass.closeTransaction());
assertTrue(testYourClass.saveCalled);
assertEquals(1, testYourClass.infoMessages.size());
assertEquals(0, testYourClass.errorMessages.size());
}
}
请注意,上述解决方案不涉及 Mockito。一旦你对测试重新设计感到满意,你可以考虑使用 Mockito 自动创建子类,如这个 SO 答案所示。
public class YourClassTest {
private YourClass stubYourClass() {
YourClass yourClass = Mockito.mock(YourClass.test, Mockito.CALLS_REAL_METHODS);
doNothing().when(yourClass).addInfoMessage(anyString());
doNothing().when(yourClass).addErrorMessage(anyString());
doNothing().when(yourClass).saveAccountInternal();
return yourClass;
}
@Test public void closeTransactionShouldSave() {
YourClass yourClass = stubYourClass();
assertNull(yourClass.closeTransaction());
verify(yourClass).saveAccountInternal();
verify(yourClass).addInfoMessage(anyString());
verify(yourClass, never()).addErrorMessage(anyString());
}
@Test public void closeTransactionShouldSave() {
YourClass yourClass = stubYourClass();
doThrow(new ResourceException()).when(yourClass).saveAccountInternal();
assertNull(yourClass.closeTransaction());
verify(yourClass).saveAccountInternal();
verify(yourClass).addErrorMessage(anyString());
verify(yourClass, never()).addInfoMessage(anyString());
}
}
当然,在信息/错误消息调用中断言"从不"可能会使你的测试比你想要的更脆弱;这只是为了表明使用手动或 Mockito 生成的子类进行测试可以为你提供这种测试所需的所有清晰度。
解决方案是第一个。您的方法除了对依赖项产生副作用外,什么都不做,并可能更改待测试对象的状态。这就是这些副作用,以及要测试的对象的新状态。
测试新状态很简单。测试依赖项的副作用是使用 Mockito 的verify()
方法完成的,如官方文档的第一项所述:
//Let's import Mockito statically so that the code looks clearer
import static org.mockito.Mockito.*;
//mock creation
List mockedList = mock(List.class);
//using mock object
mockedList.add("one");
mockedList.clear();
//verification
verify(mockedList).add("one");
verify(mockedList).clear();