Search code examples
javatestingjmockit

JMockit - verify private method


The method below has 3 possible paths: 1. True, 2. False, 3. Exception.

To test it, I need to mock the private getWootsWithAvailableProducts for the True and Exception paths. However, the collective wisdom seems to be saying that you should not mock private methods. How else can I test those paths if I don't mock the private method, and spy on it for verification. If that's all true, why is it so hard to mock private methods. If its not true, what am I missing?

Under test:

  public List<Woot> findAllWoots(final boolean isBuy) throws Exception {

    final List<Woot> allWoots = wootService.findAllWoots();

    return isBuy ? getWootsWithAvailableProducts(allWoots) : allWoots;
  }

Futher details:

getWootsWithAvailableProducts calls a public service that makes a network request. So, I can mock the actual service class and prevent any net requests from occurring.

private List<Woot> getWootsWithAvailableProducts(List<Woot> allWoots)
    throws ServiceException {

    final String stringOfWootIds = buildStringOfCommaSeparatedIDs(allWoots);
    final List<Count> categoryIDs = wootSearchService
        .getWootIDsOfAvailableProducts(stringOfWootIds);

    return filterOnlyWootsWithAvailProducts(allCategories, categoryIDs);// also private.
}

Solution

  • From comments it was indicated that getWootsWithAvailableProducts

    calls a service that makes a network request

    That is an external dependency that should be abstracted out into its own concern as it is tightly coupling your code and making it difficult to test in isolation.

    public interface WootProductsService {
        List<Woot> getWootsWithAvailableProducts(List<Woot> woots);
    }
    

    The implementation of said abstractions will encapsulate the network calls, decoupling the original code so that the abstraction can be mocked when testing the method in isolation.

    WootProductsService wootProductService; //Injected
    
    public List<Woot> findAllWoots(final boolean isBuy) throws Exception {
    
        final List<Woot> allWoots = wootService.findAllWoots();
    
        return isBuy ? wootProductService.getWootsWithAvailableProducts(allWoots) : allWoots;
    }
    

    You now have control of all the dependencies and can manipulate them as desired when testing for all scenarios.

    Pay attention to the concerns of your methods and by extension, classes, as they will indicate if your class is doing too much.

    When encountering problem in testing your code, take that as a sign that something is not right with its design. Review the cause of the problem and consider what refactors need to be applied to make the code more SOLID.