Java Programming Tutorials

Java programming tutorials with many code examples!

Spock spy and partial mocking

Problem:

How to create Spock Spy and do partial mocking? In this post we’re going to show practical application of partial mocking using Spy. Let’s test!

Let’s pretend that the following interface is Camel Processor ;-)

package com.farenda.spock;

public interface Processor {
    void process(String exchange);
}

And the code below is our ProcessorFactory that creates our complex processors and we want to unit test only its create(Strategy) method. The problem is that it calls other factory methods that do some complex setup, which we don’t want to test here.

package com.farenda.spock;

import java.util.List;

public class ProcessorFactory {

    private List<String> endpoints;

    enum Strategy {
        MULTICAST, ROUND_ROBIN, SELECTOR
    }

    public ProcessorFactory(List<String> endpoints) {
        this.endpoints = endpoints;
    }

    public Processor create(Strategy strategy) {
        switch(strategy) {
            case MULTICAST:
                return createMulticastProcessor(endpoints);
            case ROUND_ROBIN:
                return createRoundRobinProcessor(endpoints);
            case SELECTOR:
                return createSelectorProcessor(endpoints);
        }
        throw new IllegalArgumentException("Unknown strategy: " + strategy);
    }

    Processor createSelectorProcessor(List<String> endpoints) {
        // do complex things that we don't want to test
        return null;
    }

    Processor createRoundRobinProcessor(List<String> endpoints) {
        // do complex things that we don't want to test
        return null;
    }

    Processor createMulticastProcessor(List<String> endpoints) {
        // do complex things that we don't want to test
        return null;
    }
}

Solution:

Creating simple Mock is no go, because it mock all methods in a class/interface. What we want is to mock only part of the class and to do that we can use Spock Spy. Spy allows us to specify only the methods we want to mock and others leave from original object. In our case the test would look like this:

package com.farenda.spock

import spock.lang.Shared
import spock.lang.Specification

import static com.farenda.spock.ProcessorFactory.Strategy.*

class SpyTest extends Specification {

    @Shared def multicastProcessor = Mock(Processor)
    @Shared def roundRobinProcessor = Mock(Processor)
    @Shared def selectingProcessor = Mock(Processor)

    @Shared def endpoints = ['direct:x', 'direct:y']

    def factory = Spy(ProcessorFactory, constructorArgs: [endpoints]) {
        createMulticastProcessor(endpoints) >> multicastProcessor
    }

    def setup() {
        factory.createRoundRobinProcessor(endpoints) >> roundRobinProcessor
    }

    def 'should create selected processor'() {
        given:
        factory.createSelectorProcessor(endpoints) >> selectingProcessor

        when:
        def processor = factory.create(strategy)

        then:
        processor == expectedProcessor

        where:
        strategy    | expectedProcessor
        MULTICAST   | multicastProcessor
        ROUND_ROBIN | roundRobinProcessor
        SELECTOR    | selectingProcessor
    }

}

The code is fairly straightforward. Spy is created with, well, Spy method, which takes implementation class as the parameter and possibly some of its arguments. Here we pass endpoints as constructor parameter. What is interesting we can specify mocked methods on Spy in different places:

  • during Spy construction (createMulticastProcessor),
  • in setup phase (createRoundRobinProcessor),
  • in test itself (createSelectorProcessor).

Spock spies should not be overused, because it usually means that the code haven’t been written correctly (for example I’ve used Spock Spy only once during 4 years of using Spock Framework). Sometimes they are useful, though.

Share with the World!
  • UATP

    Thank you for providing these tutorials and example code for using the Spock Framework! I’m new to Spock and this has been very helpful!!

  • Eric West

    Thanks for providing this stuff! Pretty useful.

    FYI, I use an ad-blocker, so I got a popup asking to disable to support farenda. I did this, but the three ads that came up afterwards were all fear-based scare media crap, which is exactly why I use an ad-blocker in the first place. Sorry, but back on to the block list you go. :(

    • Thank you for the comment! Unfortunately I don’t choose the ads – it’s all Adsense (re)marketing and is based on calculations Google did. Sorry.