单元测试只有一个结果的方法

时间:2014-11-23 16:48:19

标签: java unit-testing mockito

我有一个Controller动作方法,无论发生什么,总是返回null,因为我希望它重新加载同一页面。 (JSF 2.2)。

我已成功使用mockito来协调执行每条可能的路径。

但是,每个路径主要调用抽象方法来添加消息或调用第三方库。

所以我可以断言它返回null,但无论如何都是这样。随着发展的继续,我看到这种模式重复。

问题:尽管有执行路径,我总是在测试null。

我看到它们可能的解决方案:

  1. 我非常绿色,所以我可以做其他事情 验证是否调用了第三方和抽象方法。
  2. 我觉得 那个可能有用但是hacky的东西是一个状态标志 某种知道什么样的消息刚刚添加到堆栈中。 Hacky因为它唯一真正的用途是它将用于测试,正如我现在所看到的那样。

  3. 重新评估我的方法,因为如果我处于这种情况,因为我的 代码设计错误。

  4. 我没有问题。保持原样,并确信我正在运行每个执行路径并验证其结果,甚至认为它是相同的。
  5. 问题: 鉴于已知的内容,您会首先考虑在单元测试中尝试解决外部验证内部执行路径的问题?或者有更好的解决方案吗?

    提前致谢。

    使用示例代码更新以解释验证问题,如果这是我应该采取的路线:

    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;
    

2 个答案:

答案 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生成的子类进行测试可以为您提供进行此类测试所需的所有清晰度。