Spock mock returns null inside collabolator but not in feature method
Asked Answered
T

1

5

I have a problem with Spock Mock() object. I have a java class I'm trying to test. This class does some ftp stuff I want to mock. My sample code

class ReceiveDataTest extends Specification{
    String downloadPath = 'downloadPath';
    String downloadRegex = 'downloadRegex';
    SftpUtils sftpUtils = Mock();
    ReceiveData receiveData;

    def setup(){
        sftpUtils.getFileNames(downloadPath,downloadRegex) >> ['file1', 'file2']
        receiveData= new ReceiveData()
        receiveData.setDownloadPath(downloadPath)
        receiveData.setDownloadRegex(downloadRegex)
        receiveData.setSftpUtils(sftpUtils);

    }

    def "test execute"() {
        given:
        def files = sftpUtils.getFileNames(downloadPath,downloadRegex)
        files.each{println it}
        when:
        receiveData.execute();
        then:
        1*sftpUtils.getFileNames(downloadPath,downloadRegex)
    }

}

public class ReceiveData(){

  //fields, setters etc

    public void execute() {
        List<String> fileNames = sftpUtils.getFileNames(downloadPath, downloadRegex);

        for (String name : fileNames) {
            //dowload and process logic
        }

    }
}

Now, inside "test execute" the files.each{} prints what is expected. But when receiveData.execute() is called my sftpUtils are returning null.. Any ideas why?

EDIT Maybe i didnt state my problem well - that I dont want to just check if getFileNames was called. I need the result to proper check the for loop. If I comment the loop inside execute, the test passes. But since I use the result of the getFilenames() method, I get a NPE execute method reaches the for loop. With mockito I would do something like this

Mockito.when(sftpUtils.getFilenames(downloadPath, downloadRegex)).thenReturn(filenamesList);
receiveData.execute();

Mockito.verify(sftpUtils).getFilenames(downloadPath, downloadRegex);
//this is what I want to test and resides inside for loop
Mockito.verify(sftpUtils).download(downloadPath, filenamesList.get(0));
Mockito.verify(sftpUtils).delete(downloadPath, filenamesList.get(0));

but I cannot use Mockito.verify() inside Spock then block

Tolerance answered 3/7, 2013 at 12:34 Comment(2)
A couple things look strange, but the main problem is that you are not repeating the >> part of the stub specification in the expectation (i.e. then block). SeeMesolithic
Maybe i didnt state my problem well - that I dont want to just check if getFileNames was called. I need the result to proper check the for loop. If I comment the loop inside execute, the test passes. But since I use the result of the getFilenames() method, I get a NPE execute method reaches the for loop.Tolerance
M
15

The main problem is that you did not include the response generator (the >> part) in the expectation (i.e. the "1 * ..." part inside the then: block).

This is explained well in the spock documentation.

You shouldn't have to declare your stub in the setup: block. You can just specifiy it once in the then: block -- even though that follows the call to receiveData.execute(). That's part of the magic of spock thanks to Groovy AST transformations. And since (non-shared) fields are reinitialized before each test (more AST based magic), you don't even need setup() in this case.

Another odd thing is that you are both stubbing out sftpUtils.getFilenames() and also calling it from the test code. Mocks and stubs are intended to replace collaborators that are called from the system under test. There's no reason to call the stub from the test driver. So delete the call to getFilenames() from your given block and let the code under test call it instead (as it does).

Groovy lets you simplify calls to Java set and get methods. Look at the initialization of receiveData below. Its okay to use def in Groovy. Let the compiler figure out the data types for you.

Leading to something like:

class ReceiveDataTest extends Specification {

    // only use static for constants with spock
    static String PATH = 'downloadPath'
    static String REGEX = 'downloadRegex'

    def mockSftpUtils = Mock(SftpUtils)

    def receiveData = new ReceiveData(downloadPath : PATH,
                                      downloadRegex : REGEX,
                                      sftpUtils : mockSftpUtils)

    def "execute() calls getFileNames() exactly once"() {
       when:
           receiveData.execute()
       then:
            1 * mockSftpUtils.getFileNames(PATH, REGEX) >> ['file1', 'file2']
            0 * mockSftpUtils.getFileNames(_,_) 

           // The second line asserts that getFileNames() is never called
           // with any arguments other than PATH and REGEX, aka strict mocking
           // Order matters! If you swap the lines, the more specific rule would never match
    }
}
Mesolithic answered 3/7, 2013 at 16:48 Comment(4)
Ok, I read carefully again the docs you specified, and indeed the test runs correctly, I get the values inside the execute(). Thanks!Tolerance
"This is explained well in the spock documentation." Sure, but your example is better.Rattigan
@Rattigan Agreed. I would say the Spock documentation doesn't explain this very well at all.Liatris
Perfect answer to problem that shouldn't exist (what a weird design decision?!).Stimulative

© 2022 - 2024 — McMap. All rights reserved.