更改单元测试的私有方法行为

时间:2014-03-25 04:00:39

标签: java unit-testing mocking mockito junit4

这不是我的代码的样子,但它将接近我想要的例子

class Phone {
    public makeCall(String number) {
        addEntryToCallLog(number)
        //eventually make a call
    }

    private void addEntryToCallLog(String number) {
        //make database calls
    }
}


class PhoneTest {
    Phone phone
    @Test
    public void canMakeACall() {
        //mock the behaviour of phone.addEntryToCallLog to prevent database exceptions
        phone.makeCall("1223");
        //Assert somehow that call was made << --IGNORE this NOT IMPORTANT
    }
}

我想测试&#34; makeCall&#34;在单元测试中,但是当我这样做时,代码会抛出一些数据库异常,从而破坏了我的测试。 我认为能够测试模拟java私有方法的行为是合理的,因为这允许所有测试的一致行为。

我过去曾使用过groovy并且使用它可以使用spock来模拟私有方法的行为。我也可以使用metaClasses为我创建的实例做同样的事情。但在java中,似乎没有一种直接的方法来做到这一点。

我也尝试过mockito和power mockito,但是他们允许我更改私有方法的返回值,但是他们不允许我改变行为。

这似乎是一个显而易见的事情,那里有人已经处理过。我想我错过了一些东西。但它是什么。

2 个答案:

答案 0 :(得分:2)

我的建议是放宽从private到包私有的可见性,可能带有@VisibleForTesting annotation/** Visible for testing. */注释。此时,您可以使用doAnswer“部分模拟”您的内部方法,正如DTing在另一个答案中提到的那样,以替换实际实现的行为。

这里的主要问题是Mockito最擅长将接口转换为存根实现。然而,私有方法非常故意不是接口的一部分,因此Java通过Mockito所依赖的一些反射特性使它们harder to access。 (在幕后,Mockito正在创建你正在嘲笑的类的子类,而子类通常不能覆盖私有方法。)

您还应该记住,理想情况下,您的单元测试应该将您的类作为一个单元进行测试,而无需深入研究实现细节。通过说你试图替换你的私有方法的行为,你发出你的类需要一个尚不存在的接缝的信号。您可以考虑重构依赖注入:

class Phone {
  interface CallLogger {
    void addEntry(String number);
  }

  private final CallLogger logger;

  public Phone() {
    this(new DefaultCallLogger());
  }

  /** Visible for testing. */
  Phone(CallLogger logger) {
    this.logger = logger;
  }

  /* ... */
}

...然后可以让你选择你想要的任何CallLogger,包括在生产中进入数据库,在测试中伪造或嘲笑它,或者在将来用批量版本替换它。同样地,使addEntryToCallLog包私有或protected:您指示子类可以更改他们向调用日志添加条目的方式,这正是您在测试中尝试做的事情,并且承认这将有助于Java VM和您的代码的读者了解行为可以在哪些方面发生变化。


所有这一切,如果您只是想使用PowerMockito来替换实现,您可以使用PowerMockito的doAnswerwhen为您的私有方法提供不同的行为。请记住,您有机会改进设计,花费更少的时间和精力来对抗实施细节。

答案 1 :(得分:1)

您的示例很奇怪,因为您的getContactFromDatabase不会返回任何内容,只是您使用它来设置Contact变量。如果你想模拟对象创建行为,请看看:

https://code.google.com/p/mockito/wiki/MockingObjectCreation

class PhoneTest {

  @Spy Phone phone;

  @Test
  public void canMakeCall() {
    doReturn(someContact)
      .when(phone)
      .getContactFromDatabase(someString);

    phone.makeCall(someString);
  }   
}

如果你想什么都不做:

class PhoneTest {

  @Spy Phone phone;

  @Test
  public void canMakeCall() {
    doNothing()
      .when(phone)
      .getContactFromDatabase(someString);

    phone.makeCall(someString);
  }   
}

http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#13

  

您可以创建真实对象的间谍。当你使用间谍然后   调用实际方法(除非方法被存根)。真正的间谍   应谨慎使用,偶尔使用,例如在交易时   遗留代码。