Mocking Sftp class skip method call

Viewed 4068
public class SFTP {

    public Map<Report, TransferStatus> transfer(List<Report> reports) {
       //testing logic here
     }

    private ChannelSftp channelSftp;
    private Session session;


    private TransferStatus send(File file) {
                connect();

                send(stream, file.getName());      
    }


    private void send(FileInputStream stream, String name) throws SftpException, IOException {

        channelSftp.put(stream, fileNameWithId, new SftpLogMonitor(), ChannelSftp.OVERWRITE);
        stream.close();
    }


    private void connect() throws JSchException {

        if (session != null && channelSftp != null) {
            return;
        }

        JSch jsch = new JSch();

        jsch.addIdentity(sftpConfig.getSftpPublicKey());

        session = jsch.getSession(sftpConfig.getSftpUser(), sftpConfig.getSftpHost(), Integer.parseInt(sftpConfig.getSftpPort()));

        session.setUserInfo(new JschUserInfo());
        session.setConfig("StrictHostKeyChecking", "no");

        session.connect();

        Channel channel = session.openChannel("sftp");
        channelSftp = (ChannelSftp) channel;
        channelSftp.connect();
    }

Given the above, how can I avoid the calls to JSch objects. I want to avoid sending of the file to remote server and just want to validate test cases in transfer() method.

So far I have the following settings:

   private JSch jSch = mock(JSch.class);
    private Session session = mock(Session.class);
    private ChannelSftp sftp = mock(ChannelSftp.class);


    @Before
    public void setUp() throws Exception {
        when(jSch.getSession(eq(USERNAME), eq(HOSTNAME), eq(PORT))).thenReturn(session);
        when(session.openChannel("sftp")).thenReturn(sftp);
        //doNothing().when(sftp).put(Matchers.any());

    }

I don't want to exclude the call to put() or pass throw it to the next statement in the method.

2 Answers

The main problem with using mockito here is that your connect() method depends on new JSch(). Mockito is not able to mock constructor calls. But you can change that with a little workaround, as suggested in Michael Feathers book. Just extract your new JSch() to the package private getter method

private void connect() throws JSchException {
    ...
    JSch jsch = getJSch();
    ...
}

JSch getJSch() {
    return new JSch();
}

Now in your test, you can override this method to return a mocked instance

private JSch jSch = mock(JSch.class);
private SFTP sftp = new SFTP(){
    @Override
    JSch getJSch() {
        return jSch;
    }
};

It's not the most elegant solution but it's the solution worth to consider. Especially if you don't want to do a lot of refactoring.

You can use below command to get the mocked instance of Jsch:

@Mock
JSch jsch;

@Mock
ChannelSftp channelSftp;

@Mock
Session session;

@Before
public void setUp() throws Exception {
    Whitebox.setInternalState(SftpConnection.class, "sftpUserName", "test1");
    Whitebox.setInternalState(SftpConnection.class, "sftpIpAddress", "test2");
    Whitebox.setInternalState(SftpConnection.class, "sftpPort", 0);
    Whitebox.setInternalState(SftpConnection.class, "sftpPortPassword", "test3");
    PowerMockito.whenNew(JSch.class).withNoArguments().thenReturn(this.jsch);
    Mockito.when(this.jsch.getSession("test1", "test2", 0)).thenReturn(this.session);
    Mockito.when(this.session.openChannel("sftp")).thenReturn(this.channelSftp);
}
Related