Mocha: Feature: option to clear require.cache between tests

Created on 10 Aug 2012  Â·  8Comments  Â·  Source: mochajs/mocha

We ran into some issues with syntax like:

mocha test1.js test2.js test3.js

Since mocha runs them all in the same process/context, if test1 manipulated the test subject at all (adding a mock, or a shim, or changing state somehow), test2 and test3 inherited those changes... so running mocha test3.js would produce different results than running it with the group.

We ended up adding xargs to scripts/test in order to run the files through mocha individually. The problem with this is, you lose the summary data (# of tests, # of failed tests, etc) and have to scroll through dozens of test summaries to figure out what's going on.

If there's no good reason against this, I might hack on it myself and submit a pull. Any opposition?

Most helpful comment

No, I agree with @hunterloftis here. I've just spent the last 3 hours tearing my hair out because a module I wrote required fs.

When I changed the file name of my test file - that changed the execution order of which meant that a test that was previously passing, began to fail because the execution order meant that my test was seeing a real instance of fs cached in require() rather than the mock that I wrote.

Bottom line - mocking out fs in one test shouldn't affect the outcome of another.

All 8 comments

IMO this is not necessary. If you need a clean slate that's what separate processes are for, there's no nice way around that

https://github.com/visionmedia/mocha/issues/492 could be used later to aggregate results

No, I agree with @hunterloftis here. I've just spent the last 3 hours tearing my hair out because a module I wrote required fs.

When I changed the file name of my test file - that changed the execution order of which meant that a test that was previously passing, began to fail because the execution order meant that my test was seeing a real instance of fs cached in require() rather than the mock that I wrote.

Bottom line - mocking out fs in one test shouldn't affect the outcome of another.

proxyquire provides a nice way to do this. You use it to load the module under test, provide whatever stubs you want, and configure it to force modules to reload. If you don't want it to call through to the real module for unstubbed methods you need to configure that as well.

https://github.com/MarkHerhold/IcedFrisby/pull/84/files#diff-d982ffbc889b93719c8ab1b0ac9a976cR11
https://github.com/MarkHerhold/IcedFrisby/pull/84/files#diff-d982ffbc889b93719c8ab1b0ac9a976cR382

Yeah, I did take a look at proxyrequire. I didn't end up using it for some reason (I forget exactly what). I ended up going with mock-require which is a similar idea.

The problem I had is that my mock-require mock wasn't getting it - it was just ignoring it and using the version of fs in the cache.

The "fix" in the end was to purge the require cache just before I loaded my module under test.

import * as mock from 'mock-require';
import {Readable}  from 'stream';

mock('fs', {
    existsSync: (path : any) => {
        return path.match(/exists/);
    },
    createReadStream: () => {
        return new Readable();
    }
});

delete require.cache[require.resolve('../../../src/media_providers/disk/DiskSource')];
import DiskSource from '../../../src/media_providers/disk/DiskSource';

describe('Disk source', () => {

    it('is able to retrieve song files from disk', () => {
        const da       = new DiskSource();
        const songName = 'thissongexists.mp3';

        return Promise.all([
            expect(da).to.respondTo('getAudio'),
            expect(da.getAudio(songName)).to.be.instanceof(Promise),
            expect(da.getAudio(songName)).to.eventually.be.an.instanceOf(Readable)
        ]);
    });
});

Part of the problem also is that I'm using TypeScript which means that some mocking tools work by requiring the unit under test themselves in a way that doesn't work with TypeScript's typing system.

My apologies for a bit of self-promotion, but I google-golf'ed my way here while dealing with similar issues. I've run into the "gee, I'd like to revert the require.cache state" issue often enough that I published https://github.com/broofa/resnap. (simple/lite capturing and restoring of cache state). In my mocha example there, you could add after(resetCache) to revert the cache back to it's pre-test state as a courtesy to other tests. It's not an ideal solution, as it requires adding code to each test, but it's about as simple as you're going to get w/out making changes to mocha itself.

I agree with @tj that this probably isn't appropriate for mocha to support. For mocha to offer a feature like this would very quickly lead to people asking for any/all JS state to be reset. (E.g. "hey, why are my setIntervals() in test A still firing in test B???") Resetting just the require() cache is only a small part of that problem.

Test frameworks should intrinsically handle the sandboxing of testing instances. I'm finding myself having to write a bunch of extra needless code just to deal with mocha's ugly implementation.

Called modules within a test should be de-cached(reset) automatically. A test that passes when ran standalone shouldn't suddenly fail due to state changes of another test when ran in tandem. This is generally an implied feature of any test framework that state based data is reset upon entry into a new test.

@broofa resnap looks cool… and doesn't seem to be much code!

@NateZimmer There are a lot of other test frameworks out there; if you don't like the way Mocha works maybe there's one you'd like better. Tap has an almost opposite design, and runs each test file in parallel in its own process. Jest is also quite different in that it includes many things you need like a mocking framework, data driven-tests, and snapshotting.

Was this page helpful?
0 / 5 - 0 ratings