Use Mockito to mock some methods but not others
Asked Answered
P

5

626

Is there any way, using Mockito, to mock some methods in a class, but not others?

For example, in this (admittedly contrived) Stock class I want to mock the getPrice() and getQuantity() return values (as shown in the test snippet below) but I want the getValue() to perform the multiplication as coded in the Stock class

public class Stock {
  private final double price;
  private final int quantity;

  Stock(double price, int quantity) {
    this.price = price;
    this.quantity = quantity;
  }

  public double getPrice() {
    return price;
  }

  public int getQuantity() {
    return quantity;
  }
  public double getValue() {
    return getPrice() * getQuantity();
  }

  @Test
  public void getValueTest() {
    Stock stock = mock(Stock.class);
    when(stock.getPrice()).thenReturn(100.00);
    when(stock.getQuantity()).thenReturn(200);
    double value = stock.getValue();
    // Unfortunately the following assert fails, because the mock Stock getValue() method does not perform the Stock.getValue() calculation code.
    assertEquals("Stock value not correct", 100.00*200, value, .00001);
}
Parasynthesis answered 20/2, 2013 at 0:59 Comment(9)
Why would you want to do that? You should either be testing the class (in which case, there should not be mocking at all) or you should mock it while testing a different class (in which case, no functionality). Why would you do a partial mock?Selfservice
Ok, this is a small example of the real thing. In reality, I am trying to avoid a call to the database, by passing in contrived values, but I want to verify that the other methods work correctly with those contrived values. Is there a better way to do this?Parasynthesis
Certainly: Move your database calls to a separate class (domain logic and database access should not be in the same class; they are two different concerns), extract its interface, use that interface to connect from the domain logic class, and mock only the interface during testing.Selfservice
I completely agree, it's hard to explain the whole picture without uploading gobs of code here, including third party libraries.Parasynthesis
@Selfservice why the need for an interface? Couldn't you just create a StockDatabaseCommunicator class and define business logic there without the need to create an interface?Nozicka
You probably could. But then, that wouldn't be "a better way to do it": Your database code is an implementation detail that you want to hide from the rest of your application, probably even move to a different package. You wouldn't want to have to recompile your domain logic every time you change a sequel statement, would you?Selfservice
@Selfservice Why shouldn't we mock some methods of the very class under test? It certainly is possible that I'm testing one method of the my class and that method calls a different method of the same class. What about this causes a useless unit test?Magen
As a rule of thumb, mocks should be used at system boundaries. If you're mocking functionality within your class under test, chances are, your test is tightly coupled to the implementation and will break more easily. And it's not going to help keeping your setup code readable. Is the test useless? Maybe not entirely. But I would still assume there's something not quite right with the design.Selfservice
'Why would you want to do that?' Don't forget that you may have to work with a class that hasn't been well designed to be decomposable for mocking etc. Quite often I'm faced with classes that require me to jump through such hoops to permit testing. Only then can I refactor with some confidenceBakst
T
982

To directly answer your question, yes, you can mock some methods without mocking others. This is called a partial mock. See the Mockito documentation on partial mocks for more information.

For your example, you can do something like the following, in your test:

Stock stock = mock(Stock.class);
when(stock.getPrice()).thenReturn(100.00);    // Mock implementation
when(stock.getQuantity()).thenReturn(200);    // Mock implementation
when(stock.getValue()).thenCallRealMethod();  // Real implementation

In that case, each method implementation is mocked, unless specify thenCallRealMethod() in the when(..) clause.

There is also a possibility the other way around with spy instead of mock:

Stock stock = spy(Stock.class);
when(stock.getPrice()).thenReturn(100.00);    // Mock implementation
when(stock.getQuantity()).thenReturn(200);    // Mock implementation
// All other method call will use the real implementations

In that case, all method implementation are the real one, except if you have defined a mocked behaviour with when(..).

There is one important pitfall when you use when(Object) with spy like in the previous example. The real method will be called (because stock.getPrice() is evaluated before when(..) at runtime). This can be a problem if your method contains logic that should not be called. You can write the previous example like this:

Stock stock = spy(Stock.class);
doReturn(100.00).when(stock).getPrice();    // Mock implementation
doReturn(200).when(stock).getQuantity();    // Mock implementation
// All other method call will use the real implementations

Another possibility may be to use org.mockito.Mockito.CALLS_REAL_METHODS, such as:

Stock MOCK_STOCK = Mockito.mock( Stock.class, CALLS_REAL_METHODS );

This delegates unstubbed calls to real implementations.


However, with your example, I believe it will still fail, since the implementation of getValue() relies on quantity and price, rather than getQuantity() and getPrice(), which is what you've mocked.

Another possibility is to avoid mocks altogether:

@Test
public void getValueTest() {
    Stock stock = new Stock(100.00, 200);
    double value = stock.getValue();
    assertEquals("Stock value not correct", 100.00*200, value, .00001);
}
Thapsus answered 20/2, 2013 at 1:2 Comment(14)
Again, this was an oversimplification of the actual problem (see my response above). But I corrected my typo above, thanks for pointing it out.Parasynthesis
I think this answer is wrong. You need to SPY an instance of the object, not to MOCK the class.Lail
@Lail I would say spying and mocking are both reasonable alternatives. It's difficult to say which is the best for this case, since the OP states that this is a simplified example.Thapsus
SHouldn't it be "Spy" instead of "Mock" as partial mocking cabe be provided by "Spy" in a better way.Saccharase
public static <T> T spy(java.lang.Class<T> classToSpy) still has the annotation @Incubating as I write in September 2016... if you really want to use partial mocking the spy method should be called on an instanceUnnecessary
How can I mock two method calls like stock.getMethod1().getMethod2()?Oomph
Stock stock = spy(Stock.class); This seems wrong, spy method seems to accept only objects not classes.Wed
Is it possible to mock a method of a spied class which is called outside the test class? I need to use a real method but mock a call to other method inside the real one.Hagai
The answer is absolutely correct. The arguments in spy method are varied due to the jar. mockito-core-1.10.19 has two overload methods each for Class Type and Object but mockito-all-1.9.5 has only one that is for Object. I hope it would help someone to understand APIs. thank you!Junejuneau
Looks like @InjectMocks does not understand the difference between @Spy and @Mock though so you end up having all methods stubbed even for spied classes?Jacksonjacksonville
+1 for pointing out the difference between doReturn(retval).when(spyObj).methodName(args) and when(spyObj.methodName(args)).thenReturn(retval)Hummock
Hi, Spy return generic and so Stock stock = spy(Stock.class) doesnt work. It shows need to have Class<Stock> stock = spy(Stock.class)Vulcanism
@Lail it depends, there are rare cases where you may need to mock the entire class and only call for the real implementation of one method.Alphonso
Your assertEquals seem to be wrongBartram
G
178

Partial mocking of a class is also supported via Spy in mockito

List list = new LinkedList();
List spy = spy(list);

//optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);

//using the spy calls real methods
spy.add("one");
spy.add("two");

//size() method was stubbed - 100 is printed
System.out.println(spy.size());

Check the 1.10.19 and 2.7.22 docs for detailed explanation.

Garica answered 24/5, 2013 at 6:52 Comment(0)
H
48

According to docs :

Foo mock = mock(Foo.class, CALLS_REAL_METHODS);

// this calls the real implementation of Foo.getSomething()
value = mock.getSomething();

when(mock.getSomething()).thenReturn(fakeValue);

// now fakeValue is returned
value = mock.getSomething();
Hegira answered 1/9, 2015 at 13:30 Comment(4)
Thank you for demonstrating how to setup a mock where the real implementation is called for all methods except the few I need to control from the test.Luker
class NaughtyLinkedList extends LinkedList { public int size() { throw new RuntimeException("don't call me");} } @Test public void partialMockNaughtLinkedList(){ List mock = mock(NaughtyLinkedList.class, CALLS_REAL_METHODS); mock.add(new Object()); // this calls the real function when(mock.size()).thenReturn(2); // For whatever reason, this lines throws the RuntimeException. assertEquals(2,mock.size()); } This doesn't work. For whatever the reason, when "when" is executed, it actually executes the method that's supposed to be mocked. Code:Fabri
The problem is "when." The "when" will actually execute the thing you want to partially mock. To avoid this there is an alternative: doReturn(). See the doReturn() at docs.mockito.googlecode.com/hg/1.9.5/org/mockito/…Fabri
Tried to implement this answer and found that when(mock.getSomething()).thenReturn(fakeValue); does not work, rather had to use doReturn(fakeValue).when(mock).getSomething()Rufous
D
21

What you want is org.mockito.Mockito.CALLS_REAL_METHODS according to the docs:

/**
 * Optional <code>Answer</code> to be used with {@link Mockito#mock(Class, Answer)}
 * <p>
 * {@link Answer} can be used to define the return values of unstubbed invocations.
 * <p>
 * This implementation can be helpful when working with legacy code.
 * When this implementation is used, unstubbed methods will delegate to the real implementation.
 * This is a way to create a partial mock object that calls real methods by default.
 * <p>
 * As usual you are going to read <b>the partial mock warning</b>:
 * Object oriented programming is more less tackling complexity by dividing the complexity into separate, specific, SRPy objects.
 * How does partial mock fit into this paradigm? Well, it just doesn't... 
 * Partial mock usually means that the complexity has been moved to a different method on the same object.
 * In most cases, this is not the way you want to design your application.
 * <p>
 * However, there are rare cases when partial mocks come handy: 
 * dealing with code you cannot change easily (3rd party interfaces, interim refactoring of legacy code etc.)
 * However, I wouldn't use partial mocks for new, test-driven & well-designed code.
 * <p>
 * Example:
 * <pre class="code"><code class="java">
 * Foo mock = mock(Foo.class, CALLS_REAL_METHODS);
 *
 * // this calls the real implementation of Foo.getSomething()
 * value = mock.getSomething();
 *
 * when(mock.getSomething()).thenReturn(fakeValue);
 *
 * // now fakeValue is returned
 * value = mock.getSomething();
 * </code></pre>
 */

Thus your code should look like:

import org.junit.Test;
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

public class StockTest {

    public class Stock {
        private final double price;
        private final int quantity;

        Stock(double price, int quantity) {
            this.price = price;
            this.quantity = quantity;
        }

        public double getPrice() {
            return price;
        }

        public int getQuantity() {
            return quantity;
        }

        public double getValue() {
            return getPrice() * getQuantity();
        }
    }

    @Test
    public void getValueTest() {
        Stock stock = mock(Stock.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));
        when(stock.getPrice()).thenReturn(100.00);
        when(stock.getQuantity()).thenReturn(200);
        double value = stock.getValue();

        assertEquals("Stock value not correct", 100.00 * 200, value, .00001);
    }
}

The call to Stock stock = mock(Stock.class); calls org.mockito.Mockito.mock(Class<T>) which looks like this:

 public static <T> T mock(Class<T> classToMock) {
    return mock(classToMock, withSettings().defaultAnswer(RETURNS_DEFAULTS));
}

The docs of the value RETURNS_DEFAULTS tell:

/**
 * The default <code>Answer</code> of every mock <b>if</b> the mock was not stubbed.
 * Typically it just returns some empty value. 
 * <p>
 * {@link Answer} can be used to define the return values of unstubbed invocations. 
 * <p>
 * This implementation first tries the global configuration. 
 * If there is no global configuration then it uses {@link ReturnsEmptyValues} (returns zeros, empty collections, nulls, etc.)
 */
Disgorge answered 19/2, 2016 at 11:47 Comment(2)
Well spotted... but can I just ask why you use withSettings()... like that? It appears that org.mockito.internal.stubbing.answers.CallsRealMethods() (for example) might do the job... and the javadoc for this class specifically says it's for use for partial mocks...Unnecessary
Also... won't this run into the problem encountered by other answers here: i.e. thenReturn will actually execute the method (which might cause problems, although not in this example), and so doReturn is preferable in such a case...?Unnecessary
N
3

Partial mocking using Mockito's spy method could be the solution to your problem, as already stated in the answers above. To some degree I agree that, for your concrete use case, it may be more appropriate to mock the DB lookup. From my experience this is not always possible - at least not without other workarounds - that I would consider as being very cumbersome or at least fragile. Note, that partial mocking does not work with ally versions of Mockito. You have use at least 1.8.0.

I would have just written a simple comment for the original question instead of posting this answer, but StackOverflow does not allow this.

Just one more thing: I really cannot understand that many times a question is being asked here gets comment with "Why you want to do this" without at least trying to understand the problem. Escpecially when it comes to then need for partial mocking there are really a lot of use cases that I could imagine where it would be useful. That's why the guys from Mockito provided that functionality. This feature should of course not be overused. But when we talk about test case setups that otherwise could not be established in a very complicated way, spying should be used.

Network answered 24/1, 2017 at 20:49 Comment(2)
I feel this answer is partially an opinion. Please consider editing.Direful
Upvoted to cheer up the new member in the family. No need to get this in -ve zone, nothing really technically wrong there or incorrect language/tone. Be kind on new members.Thanks.Average

© 2022 - 2024 — McMap. All rights reserved.