Mockito Spy - stub before calling the constructor

前端 未结 3 2134
悲哀的现实
悲哀的现实 2020-12-24 12:40

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.
My class looks like that:

         


        
相关标签:
3条回答
  • 2020-12-24 13:24

    To answer your question directly, you cannot use Mockito to stub a method called from the constructor. 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.

    More generally, as mentioned in Effective Java item 17, you should not call overridable methods from constructors. 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. It probably won't get you in trouble here, but it's a bad habit in 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:

    /* ... */
      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.

    0 讨论(0)
  • 2020-12-24 13:25

    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. This way the default constructor won't call its implementation of setup, it will call the overwritten method instead.
    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.


    If there is a simpler way, please answer the question because I'm not 100% content with my solution.

    0 讨论(0)
  • 2020-12-24 13:26
    1. Use 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.

    Like so:

    doAnswer(new Answer<Void>() {
          @Override
          public Void answer(InvocationOnMock invocation) throws Throwable {
               // code here
               return null;
          }
     }).when(Myclass.class, "setup");
    
    0 讨论(0)
提交回复
热议问题