我有一个Controller动作方法,无论发生什么,总是返回null,因为我希望它重新加载同一页面。 (JSF 2.2)。
我已成功使用mockito来协调执行每条可能的路径。
但是,每个路径主要调用抽象方法来添加消息或调用第三方库。
所以我可以断言它返回null,但无论如何都是这样。随着发展的继续,我看到这种模式重复。
问题:尽管有执行路径,我总是在测试null。
我看到它们可能的解决方案:
我觉得 那个可能有用但是hacky的东西是一个状态标志 某种知道什么样的消息刚刚添加到堆栈中。 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;
答案 0 :(得分:0)
解决方案是第一个。除了对依赖项的副作用之外,您的方法什么都不做,并且可能会更改被测对象的状态。这就是那些副作用,以及您想要测试的对象的新状态。
测试新状态很简单。使用Mockito的verify()
方法测试对依赖项的副作用,如very first item of the official documentation中所述:
//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();
答案 1 :(得分:0)
看起来至少有两个结果,所以覆盖它们很重要。您可以稍微重新设计该类以使测试变得容易。
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自动创建子类,如this SO answer中所示。
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());
}
}
当然,断言&#34;永远&#34;信息/错误消息调用可能会使您的测试比您想要的更脆弱;这只是为了表明使用手册或Mockito生成的子类进行测试可以为您提供进行此类测试所需的所有清晰度。