简体   繁体   English

Mockito间谍 - 在调用构造函数之前存根

[英]Mockito Spy - stub before calling the constructor

I'm trying to spy on an Object and I want to stub a method that is called by the constructor before the constructor calls it. 我正在试图窥探一个Object,我想在构造函数调用之前存根一个由构造函数调用的方法。
My class looks like that: 我的班级看起来像这样:

public class MyClass {
    public MyClass() {
         setup();
    }

    public void setup() {

    }
}

The setup method mustn't be called. 不得调用安装方法。 Well, how do I spy on this method (and stub setup so that it does nothing)? 那么,我如何监视这个方法(和存根设置,以便它什么都不做)?
It works fine with mocking the method but I want to unit test MyClass and so I will need very other method. 它可以很好地模拟方法,但我想单元测试MyClass ,所以我需要非常其他的方法。


The reason why need to stub the setup method so that it does nothing: 之所以需要存根设置方法以便它什么都不做:
I'm programing a Lego robot (lejos) and I put some code in setup that the robot needs to work. 我正在编写一个Lego机器人(lejos),我在机器人需要工作的设置中放了一些代码。 However, when I call it outside TinyVM (the VM that is installed on the robot), java crashes since it the VM hasn't been initialized properly (because the tests run on my PC). 但是,当我在TinyVM(安装在机器人上的VM)之外调用它时,java崩溃,因为VM尚未正确初始化(因为测试在我的PC上运行)。 For unit-testing the setup isn't important. 对于单元测试,设置并不重要。
I can't stub the classes/methods setup calls since some of them are public static final variables. 我不能存根类/方法设置调用,因为它们中的一些是公共静态最终变量。

To answer your question directly, you cannot use Mockito to stub a method called from the constructor . 要直接回答您的问题, 您不能使用Mockito来存根从构造函数调用的方法 Mockito needs an instance of the class before you can begin mocking, and you haven't given yourself a way to create an instance for testing. 在你开始模拟之前,Mockito需要一个类的实例,而你没有给自己创建一个测试实例的方法。

More generally, as mentioned in Effective Java item 17, you should not call overridable methods from constructors . 更一般地说,如Effective Java第 17项中所述, 您不应该从构造函数中调用可覆盖的方法 If you do so, for instance, you could provide an override in a subclass that refers to a final field but that runs before the final field is set. 例如,如果这样做,您可以在子类中提供覆盖,该子类引用final字段但在final字段设置之前运行。 It probably won't get you in trouble here, but it's a bad habit in Java. 它可能不会让你遇到麻烦,但它在Java中是一个坏习惯。

Luckily, you can restructure your code to do this very easily: 幸运的是,您可以重构代码以轻松完成此操作:

public class MyClass {
  public MyClass() {
    this(true);
  }

  /** For testing. */
  MyClass(boolean runSetup) {
    if (runSetup) {
      setup();
    }
  }

  /* ... */
}

To make it even more obvious, you can make the one-parameter MyClass constructor private, and provide a public static factory method: 为了使它更加明显,您可以将单参数MyClass构造函数MyClass私有,并提供public static工厂方法:

/* ... */
  public static MyClass createForTesting() {
    return new MyClass(false);
  }

  private MyClass(boolean runSetup) {
/* ... */

Though some developers think it is a bad practice to write any code in methods that is used mostly for tests, remember that you are in charge of the design of your code, and tests are one of few consumers you absolutely know you will need to accommodate. 虽然一些开发人员认为在主要用于测试的方法中编写任何代码是一种不好的做法,但请记住,您负责代码的设计,而测试是您绝对知道需要适应的少数消费者之一。 Though it's still a good idea to avoid explicit test setup in "production" code, creating extra methods or overloads for the sake of testing will usually make your code cleaner overall and can drastically improve your test coverage and readability. 虽然在“生产”代码中避免显式测试设置仍然是一个好主意,但为了测试而创建额外的方法或重载通常会使您的代码整体更清晰,并且可以大大提高您的测试覆盖率和可读性。

Thanks for the suggestions, but it was a little bit too complex. 感谢您的建议,但有点过于复杂。
I ended up mocking the method by extending the class and overwriting my setup method. 我最后通过扩展类并覆盖我的setup方法来模拟该方法。 This way the default constructor won't call its implementation of setup, it will call the overwritten method instead. 这样默认构造函数不会调用它的setup实现,而是调用覆盖的方法。
Here is the code: 这是代码:

// src/author/MyClass.java

public class MyClass {
    public MyClass() {
        setup();
    }

    protected void setup() {
        throw new Exception("I hate unit testing !");
    }

    public boolean doesItWork() {
        return true;
    }
}

// test/author/MyClass.java

public class MyClassTest {
    private class MockedMyClass extends MyClass {
        @Override
        protected void setup() {

        }
    }

    private MyClass instance;

    @Before
    public void setUp() { // Not to be confusing with `MyClass#setup()`!
        instance = new MockedMyClass();
    }

    @Test
    public void test_doesItWork() {
        assertTrue(instance.doesItWork());
    }

}

If you don't want MyTest's setup method to do called or overwritten by other subclasses except your test (because other developer might mess things up very badly by using the setup method), just change the visibility to default and only your classes will be able to call setup. 如果您不希望MyTest的安装方法被其他子类调用或覆盖,除了您的测试(因为其他开发人员可能会使用安装方法非常糟糕),只需将可见性更改为默认值,只有您的类才能够呼叫设置。


If there is a simpler way, please answer the question because I'm not 100% content with my solution. 如果有一种更简单的方法,请回答这个问题,因为我不是100%满意我的解决方案。

  1. Use PowerMock. 使用PowerMock。

  2. After you've imported the libraries, set it up to manipulate the class you want to mock with the instance method that mustn't be called. 导入库后,将其设置为使用不能调用的实例方法操作要模拟的类。

Like so: 像这样:

@RunWith(PowerMockRunner.class)
@PrepareForTest({<Other classes>, Myclass.class})
  1. Suppress the method at the start of your test. 在测试开始时取消该方法。

Like so: 像这样:

suppress(method(Myclass.class, "setup"));
  1. Customize the behaviour of your setup() method as desired, in your test. 在测试中根据需要自定义setup()方法的行为。

Like so: 像这样:

doAnswer(new Answer<Void>() {
      @Override
      public Void answer(InvocationOnMock invocation) throws Throwable {
           // code here
           return null;
      }
 }).when(Myclass.class, "setup");

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM