Search code examples
node.jsexpressjestjsssh2-sftp-client

Jest test a function integrated with the ssh2-sftp-client library


I need to test a function that write a file from local path to remote path on an sftp machine using shh2-sftp-client library.

my own function is the following:

 //fileshareAPI.js
 saveFile: function saveFile(folder, filename, file) {
    uploadValidateBody(folder, filename, file);
    const Client = require('ssh2-sftp-client');
    const sftp = new Client();
    return sftp.connect(sftpConfig)
    .then(() => {
        console.log('connection OK')
        fs.writeFile(filename, file, { encoding: 'base64' }, (err) => console.log(err));
        console.log(`${basePath}${folder}`)
        return sftp.mkdir(`/${basePath}${folder}`, true);
    }).then(() => {
        return sftp.put(filename, `/${basePath}${folder}/${filename}`);
    }).then(() => {
        fs.unlinkSync(filename);
        return true;
    }).catch((err) => {
        console.log('an erro occurred')
        throw new InternalServerError(5002, err.message)
    }).finally(() => {return sftp.end()})
}

I created my own test class

const sftp = require('ssh2-sftp-client') //import of the class from the library
jest.mock('ssh2-sftp-client') //default mock. All methods are undefined
describe('test', () => {
beforeAll(() => {
    sftp.mockImplementation(() => {
        return {
            connect: jest.fn().mockReturnValue(Promise.reject(new Error('error'))),
            mkdir: jest.fn().mockReturnValue(Promise.resolve(true)),
            put: jest.fn().mockReturnValue(Promise.resolve(true)),
            end: jest.fn().mockReturnValue(Promise.resolve(true))
        }
    })
});
it('should throw exception', () => {
    return expect(fileshareUtilities.saveFile('a', 'b', 'c')).rejects.toThrow('error')
});
});

Despite declaring the new constructor via the mockImplementation method, I always get the constructor returned by the default mock. In fact i get the following error:

TypeError: Cannot read property 'then' of undefined

  65 |     saveFile: function saveFile(folder, filename, file) {
  66 |         uploadValidateBody(folder, filename, file);
> 67 |         return sftp.connect(sftpConfig)
     |                ^
  68 |         .then(() => {
  69 |             console.log('connection OK')
  70 |             fs.writeFile(filename, file, { encoding: 'base64' }, (err) => console.log(err));

  at Object.saveFile (lib/apis/fileshare/fileshareAPI.js:67:16)
  at Object.<anonymous> (test/fileshare.test.js:30:42)

How do I override the different constructor methods for each test, so that I can test the various points in the chain of functions called in saveFile?


Solution

  • I hope it will be useful to others, but I have found the solution. I always defined the mock by default, but then I invoked the mockImplementation() method on the instance of the ssh2-sftp-client library class, mocking the single functions. For each test I overwrite the return value of each function.

    Here an example:

    const sftp = require('ssh2-sftp-client');
    const mockConnect = jest.fn();
    const mockMkdir = jest.fn();
    const mockPut = jest.fn();
    const mockEnd = jest.fn();
    const mockRename = jest.fn();
    sftp.mockImplementation(() => {
      return {
        connect: mockConnect,
        mkdir: mockMkdir,
        put: mockPut,
        rename: mockRename,
        end: mockEnd
     }
    });
    
    
    it('test mkdir', async() => {
        mockConnect.mockReturnValue(Promise.resolve(true));
        mockMkdir.mockReturnValue(Promise.reject(new Error('permission danied mkdir')))
        await expect(fileshareUtilities.saveFile(inputFolder, inputFilename,   'file')).rejects.toThrow(new InternalServerError(5002, "permission danied mkdir"));
    });