0

I am performing a unit test for addPortfolioTrans method below. But I'm not sure how do I mock the method validateSellAction(portfolioTrans) without mocking PortfolioTransServiceImpl class.

PortfolioTransServiceImpl class:

@Override
public PortfolioTrans addPortfolioTrans(PortfolioTrans portfolioTrans, long portId, String username) {
    
    StockWrapper stockWrapper = portfolioHoldService.findStock(stockSym);
    if ((stockWrapper.getStock() == null || stockWrapper.getStock().getQuote().getPrice() == null)) {
            portfolioTrans.setErrMsg("Stock Symbol is invalid, unable to get stock price. Save transaction failed.");
            return portfolioTrans;
    }

    if (portfolioTrans.getAction().equals(ConstantUtil.SELL_ACTION)) {
        sellActionCheck = validateSellAction(portfolioTrans);
    }
}

I wanted to do something like this below for my test class. But I am not sure how to do it without mocking an object. My objective is to return a -1 value after mocking the validateSellAction method.

TestPortfolioTransServiceImp class:

@InjectMocks
@Autowired
private PortfolioTransServiceImpl portfolioTransServiceImpl;

@Mock
PortfolioHoldService portfolioHoldService;

@Test
void testAddPortfolioTrans_sellSuccess() {
    Long portId = 2L;
    String username = "user1";

        //edited and added this portion
        Stock stock = new Stock("MSFT");
        stock.setStockExchange("NasdaqGS");
        stock.setName("Microsoft Corp.");
        StockQuote stockQuote = new StockQuote("MSFT");
        stockQuote.setPrice(new BigDecimal("280.04"));
        stock.setQuote(stockQuote);
        StockWrapper dummyStockWrapper = new StockWrapper(stock);
        
        //i have other dependencies with other services
        when(portfolioHoldService.findStock(anyString())).thenReturn(dummyStockWrapper);

    PortfolioTrans portfolioTransObj = new PortfolioTrans();
    portfolioTransObj.setStockName("Microsoft Corp.");
    portfolioTransObj.setAction("SELL");
    portfolioTransObj.setNoOfShare(50);
    portfolioTransObj.setStockSymbol("MSFT");
    portfolioTransObj.setStockExchg("NASDAQ");
    portfolioTransObj.setTransPrice(new BigDecimal("269.81"));

    PortfolioTrans result = portfolioTransServiceImpl.addPortfolioTrans(portfolioTransObj, 0, username);

    // wanted to mock return value below, but not sure how to do it without mocking object
    when(validateSellAction(portfolioTrans)).thenReturn(-1);

    // assert statements below
}
calai
  • 3
  • 5

1 Answers1

2

What you're looking for can be achieved by "spying" on an object - creating a spy object based on an actual object instance (thanks to @Jeff Bowman for the clarifying comment). It allows you to verify calls to certain methods, but also partially redefine the object's behaviour, for example mocking a single method, while preserving behaviour of all the others. You can read about Mockito spies in the docs or many other sources on the Internet.

What you have to do is either annotate the field with a @Spy annotation, call the spy(...) method explicitly or use a @SpyBean annotation, if you're using Spring (which I assume might be the case, since there's @Autowired over a field).

The approach is called partial mocking. You can also use mocks and tell Mockito to call actual methods implementation instead of mocking all the rest (by using doCallRealMethod or thenCallRealMethod described in the docs link above).

Jonasz
  • 1,617
  • 1
  • 13
  • 19
  • 1
    I think partial mocking is what the OP is asking for, but be careful: I would describe spying as creating a spy _based on_ an instance but not _wrapping_ an instance, since Mockito will actually [create a new subclass instance](https://javadoc.io/static/org.mockito/mockito-core/4.6.1/org/mockito/Mockito.html#13) with overridden methods and then copy instance state into it. [This can also matter for anonymous inner classes.](https://stackoverflow.com/q/33656249/1426891) – Jeff Bowman Aug 08 '22 at 21:03
  • Thanks for the replies. I actually DO NOT want to call the real method validateSellAction(portfolioTrans). I want to mock it to return a -1 value. On top of this, i wanted to mock other methods from other dependencies also. Edited the code above. – calai Aug 10 '22 at 03:21
  • 1
    Is it worth challenging why you need to mock `validateSellAction`? In general, unit tests that assume less about internal implementation (and just focus on input/output) better survive later changes. Does `validateSellAction` make some external service call, and if so could you mock that instead? Or if it has a side effect, could you extract that side effect into a service that you inject into this class? – Mark Peters Aug 10 '22 at 03:33
  • 1
    @MarkPeters Ok, that is one way of resolving. I think that helps. Thanks. I can actually mock validateSellAction using another service, e.g. when(portfolioTransDao.validateSellAction(portfolioTransObj)).thenReturn(-1); – calai Aug 10 '22 at 03:43