Mockito Spy - stub before calling the constructor

33,855

Solution 1

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.

Solution 2

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.

Solution 3

  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");

Solution 4

@fragorl 's answer will work for static class method, but fail on instance method. If you need to mock/spy instance method, instead of suppress do replace:

PowerMockito.replace(MemberMatcher.method(Myclass.class, "setup")).with(new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] arguments) throws Throwable {
                //add some special behaviour
                //if needed can call original method afterwards in that way:
                //method.invoke(o, arguments);
                return null;
            }
        });
Share:
33,855
Matt3o12
Author by

Matt3o12

Updated on September 06, 2021

Comments

  • Matt3o12
    Matt3o12 almost 3 years

    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:

    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.


    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. 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). 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.

  • Michael Osofsky
    Michael Osofsky over 9 years
    Hi @JeffBowman, thanks for answering the question directly: it's not possible with Mockito. The trouble is you can't always change the code you're testing. The PowerMock answer from fragorl is great because it allows one to test code that depends on 3rd party libraries you can't control.
  • Michael Osofsky
    Michael Osofsky over 9 years
    Wow @fragorl, PowerMock seems ideal for this! But does PowerMock have parity with Mockito?
  • Michael Osofsky
    Michael Osofsky over 9 years
    I like your answer best (I suggest you "accept it") because it doesn't require learning another testing framework (i.e. PowerMock). Did you try using try/catch to catch when the TinyVM pukes?
  • Michael Osofsky
    Michael Osofsky over 9 years
    Oops, never mind, my idea won't work because even though you catch the Exception when TinyVM pukes, you still won't have what you need--a non-null MyClass instance. So I like your answer the best.
  • Jeff Bowman
    Jeff Bowman over 9 years
    @Michael: PowerMock exists, and can be helpful, but using it there would violate a more-important rule: Don't mock types you don't own. If the component you're testing is responsible for interacting with a third-party service, and you test against a fake implementation of what you think the third-party service does, what are you actually testing? If your component has business logic and third-party interaction, why aren't those two separate tightly-scoped components instead?
  • Michael Osofsky
    Michael Osofsky over 9 years
    Thanks, I've just read a few of your posts to understand your point better. Do you have something I could read to see an example of designing separate tightly-scoped components that separate business logic and third-party interaction?
  • Daniel Hajduk
    Daniel Hajduk over 8 years
    If im not mistaken, when(class) works only on static methods, and the method @MichaelOsofsky tried to mock is an instance method. Doesnt seem like there is a way to mock it other than overriding class that has non parameter constructor. With parameter constructor if class doesnt have non one, its impossible also to do it.
  • Mustafa Mohammadi
    Mustafa Mohammadi almost 5 years
    This is the best answer