I'm testing a YouTube downloader I wrote that uses the @distube fork of the ytdl-core library. I'm having trouble mocking the library because the exported ytdl variable is both a namespace and function:
const ytdl = (link, options) => {
const stream = createStream(options);
ytdl.getInfo(link, options).then(
info => {
downloadFromInfoCallback(stream, info, options);
},
stream.emit.bind(stream, "error"),
);
return stream;
};
module.exports = ytdl;
ytdl.getBasicInfo = getInfo.getBasicInfo;
ytdl.getInfo = getInfo.getInfo;
// etc...
I found this stackoverflow question but there's no approved answer and I couldn't get any of the proposed solutions to work. The best I could do is append the ytdl object to my own object and then export that:
const distube = {};
distube.ytdl = require("@distube/ytdl-core");
Then in the test.js file:
jest.spyOn(distube, 'ytdl').mockImplementation(() => { //do stuff });
I couldn't figure out a way to mock the ytdl object directly. I was able to mock individual methods using jest.spyOn(ytdl, 'methodName'), but not the ytdl() alias function.
The ytdl object is being used in an express app, which I exported for mocking purposes. I am using the light-my-request library to run the express app. I was originally using Supertest, but it didn't work for what I was doing. I logged a bug ticket for the issue.
Here is the code that invokes the server POST request handler:
const inject = require('light-my-request');
it('should treat empty strings as no value)', async () => {
const result = await inject(app, {
method: 'POST',
url: '/',
headers: { 'Accept': 'multipart/form-data' },
payload: {
url: PLAYLIST_URL+VALID_PLAYLIST_ID,
limit: ''
},
});
expect(result).toBeArray();
});
I was able to mock the ytdl() function by appending it to my own distube object, as described above. In fact, the best way to mock the ytdl function and methods so far has been to override the existing - "real" - one with my own:
distube.ytdl = () => {
//do stuff
};
distube.ytdl.getInfo = () => ('false');
That being said, I could not mock the @distube/ytdl-core module directly. Doing so had no effect on the server's ytdl instance:
Does anyone know of a way to mock both the ytdl() function and getInfo() method? Something like:
jest.mock(ytdl, () => {
const ytdl = jest.fn().mockImplementation();
ytdl.getInfo = jest.fn().mockImplementation();
return ytdl;
});
I added a simple tester app on github.
Answer based on the Github repo and comments:
This is what I understood from your question: the goal is to mock the ytdl library, so when you test the api it won't call the actual ytdl library.
You have got few things wrong in your tests:
jest.mock
doesn't work inside the describe
block. Check this@distube/ytdl-core
. The jest will take care of using your mocked ytdl
in the place of real ytdl
object.Here is the full working code for test.js:
let {
app
} = require('../server.js');
const ytdl = require("@distube/ytdl-core");
const matchers = require('jest-extended');
expect.extend(matchers);
//const inject = require('light-my-request');
const supertest = require('supertest');
const api = supertest(app);
beforeEach(() => {
jest.clearAllMocks();
});
// jest.mock doesn't work inside the describe block, so moved it outside.
// You can directly mock the `@distube/ytdl-core`.
jest.mock('@distube/ytdl-core', () => {
let ytdl;
ytdl = jest.fn().mockImplementation(() => {
// This will call the mocked getInfo.
// So you can test expectations on it.
ytdl.getInfo();
return 'mocked ytdl()'
});
ytdl.getInfo = jest.fn().mockImplementation(() => 'mocked getInfo()');
return ytdl;
});
describe('The ytdl library', () => {
it('should extract the audio from the video', async () => {
const result = await api.post('/').responseType('blob');
expect(result).not.toBe(null);
// Assert that the mocked ytdl has been called
expect(ytdl).toHaveBeenCalled();
// Assert that the mocked ytdl.getInfo has been called
expect(ytdl.getInfo).toHaveBeenCalled();
expect(ytdl()).toBe('mocked ytdl()');
expect(ytdl.getInfo()).toBe('mocked getInfo()');
}, 10000);
});
I did a thorough testing to make sure that the above test code doesn't call the actual libray by placing several console.log
statements in the ytdl library's index.js
file.
Hope this solves your issue.