Home > Back-end >  Verify Spock mock asynchonous interaction after an exception is thrown
Verify Spock mock asynchonous interaction after an exception is thrown

Time:11-24

I am trying to use a Blocking Variable to verify an asynchonous interaction after a collaborator's method fails. This is what I got:

public class ServiceAImpl {
    private final ServiceB serviceB;
    private final ServiceC serviceC;

    @Override
    public void createAccount(String userId) {
        try {
            serviceB.createUserAccount(userId);
        } catch (Exception e) {
            asyncSaveCreationError(e);
            throw e;
        }
    }

    private void asyncSaveCreationError(Exception e) {
        runAsync(() -> saveCreationError(e));
    }

    private void saveCreationError(Exception e) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);

        serviceC.save(pw.toString());
    }
}

class ServiceAImplTest extends Specification {
    def serviceB = Mock(ServiceB)
    def serviceC = Mock(ServiceC)

    @Subject
    def subject = new ServiceAImpl(serviceB, serviceC)

    def "createAccount - should throw Exception and save error"() {
        given:
        def result = new BlockingVariable<Boolean>(10)

        when:
        subject.createAccount("userId")

        then:
        1 * serviceB.createUserAccount(_) >> { throw new Exception() }
        thrown(Exception)
        1 * serviceC.save(_) >> { result.set(true) }
        result.get()
        0 * _
    }
}

Exception is thrown, but the blocking variable is never set so I'm getting the following error:

BlockingVariable.get() timed out after 10.00 seconds
    at spock.util.concurrent.BlockingVariable.get(BlockingVariable.java:113)*

I tried to use a plain Thread.sleep but always failed the interaction of serviceC.save

CodePudding user response:

The thing that breaks you code, is that mock interactions are the first thing that is evaluated in every then block, even if you list other things first. So for it to work, you need to use a second then block as explained in the chapter invocation order in the docs. Since, the mocks are evaluated first, the execution doesn't even reach result.get() that would wait long enough so that the save interaction could be triggered.

This should fix your problem:

class ServiceAImplTest extends Specification {

    ServiceB serviceB = Mock()
    ServiceC serviceC = Mock()

    @Subject
    def subject = new ServiceAImpl(serviceB, serviceC)

    def "createAccount - should throw Exception and save error"() {
        given:
        def result = new BlockingVariable<Boolean>(10.0)
        
        when:
        subject.createAccount("userId")
        

        then: 'first catch the exception and wait for the result'
        thrown(Exception)
        result.get()
        
        then: 'verify mock interactions'
        1 * serviceB.createUserAccount(_) >> { throw new Exception() }
        1 * serviceC.save(_) >> { result.set(true) }
        0 * _
    }
}

You can test the MCVE on the Groovy Web Console

  • Related