[jest-plugin-fs] Add support for spyfs to fs plugin
Hi,
Do you think the fs plugin could also make use of spyfs package to mock fs and intercept fs requests in flight?
Description
Screenshot
Test Plan
Hi @streamich interesting concept. How could this be used in a testing scenario? Do you have an example of what the API would look like for the jest-plugin-fs plugin?
You could export a spyfs object that allows to mock, intercept and listen to file system operations.
import fs, {spyfs} from 'jest-plugin-fs';
jest.mock('fs', () => require('jest-plugin-fs/mock'));
describe('FileWriter', () => {
it('should handle error gracefully', () => {
spyfs.on('openSync', action => action.reject('Open failed'));
FileWriter.doSomething();
});
});
@streamich sorry for the late reply. How would assertions look in this case? Why can't I just use a mock on the fs object to listen for specific methods being called?
You can, but it is not just about listening for mock being called. You can also implement a mock or fail some method.
Also if you mock a method it will no longer do the actual function, at least by default.
Let's say you want to test some method opens /file.txt asynchronously. With spyfs right now it could be something like this:
import fs, {spyfs} from 'jest-plugin-fs';
it('should open /file.txt', () => {
openFileTxt();
return new Promise((resolve) => {
spyfs.on('open', action => {
expect(actions.args[0]).toBe('/file.txt');
resolve();
})
});
});
This is probably not the most elegant example, but something like that. How would you check if some file was opened using current setup.
With a bit of work we could simplify it further:
import fs, {spyfs} from 'jest-plugin-fs';
it('should open /file.txt', () => {
openFileTxt();
return spyfs.once.open({args} => expect(args[0]).toBe('/file.txt'));
});
In this example spyfs.once.open would return a Promise that listens only for the next immediate call to fs.open, and the callback inside it has the standard spyfs semantics, like it receives the action object.
This is interesting and I like where this is going! For the second example you provided, I would expect a custom matcher that achieves what you're doing. It could use spyfs under the hood but I'm thinking the syntax for the test would be something like:
it('should open /file.txt', () => {
expect(openFileTxt).toOpenFile('/file.txt');
});
it('should write once to /file.txt', () => {
expect(writeOnceFileTxt).toWriteOnceToFile('/file.txt');
});
Does that make sense? These custom matchers would utilize spyfs under the hood.
I like the idea of custom matchers.
Is custom matchers something supported by Jest? Or we can just bolt them on expect method?
I'm thinking it might have to be a callback, just like when using .toThrow() assertion:
it('should open /file.txt', () => {
expect(() => openFileTxt()).toOpenFile('/file.txt');
});
Also would be nice to somehow chain the assertions:
it('something /file.txt', () => {
const expected = expect(() => openFileTxt());
expected.toOpenFile('/file.txt');
expected.toWriteOnceToFile('/file.txt');
});
// or
it('something /file.txt', () => {
expect(() => openFileTxt())
.toOpenFile('/file.txt')
.toWriteOnceToFile('/file.txt');
});
yep! supported and encouraged: https://facebook.github.io/jest/docs/en/expect.html#expectextendmatchers
Yeah, it's a callback, the example I posted above was treating the function as a callback. () => openFileTxt() is the same as openFileTxt in this case