Open adjavaherian opened 9 years ago
There's some logic in blanket-stub-jsx.js:
delete require.cache[normalizedFilename]; // might not be stubbed in the future.
which looks like it didn't make it into jsx-stub-transform.js. Want to try adding it and see if it fixes the problem?
Zomg! I think that worked. I just added it before returning the stub. Here's my slightly modified version.
// Based on https://github.com/Khan/react-components/blob/master/test/compiler.js
var fs = require('fs'),
ReactTools = require('react-tools');
// A module that exports a single, stubbed-out React Component.
var reactStub = 'module.exports = require("react").createClass({render:function(){return null;}});';
// Should this file be stubbed out for testing?
function shouldStub(filename) {
if (!global.reactModulesToStub) return false;
// Check if the file name ends with any stub path.
var stubs = global.reactModulesToStub;
for (var i = 0; i < stubs.length; i++) {
if (filename.substr(-stubs[i].length) == stubs[i]) {
console.log('should stub', filename);
return true;
}
}
return false;
}
// Transform a file via JSX/Harmony or stubbing.
function transform(filename) {
if (shouldStub(filename)) {
delete require.cache[filename];
return reactStub;
} else {
var content = fs.readFileSync(filename, 'utf8');
return ReactTools.transform(content, {harmony: true});
}
}
// Install the compiler.
require.extensions['.jsx'] = function(module, filename) {
return module._compile(transform(filename), filename);
};
This is awesome. Not sure we should be messing with require.extensions, but thanks. =)
Perhaps we can formalize this a bit and create a re-usable, plugable solution for the other Flux and React-Router test runners out there.
Sorry if I'm derailing this discussion, but I'm honestly curious about this too. I started writing code to get React to play nicely with Mocha and eventually came across this repo, which was doing basically the same thing :)
This is awesome. Not sure we should be messing with require.extensions, but thanks. =)
Do you know of any better way to workaround this? It looks like coffeescript was doing it this way as well, so I started writing it in a similar way. Looks like this project took a similar approach. I came across this discussion which gave some perspective.
Perhaps we can formalize this a bit and create a re-usable, plugable solution for the other Flux and React-Router test runners out there.
I'd love to see this as well, maybe we can collaborate on something? My info's on my profile if you're interested in talking about how to implement this. On a sort of unrelated note, I'd love to try and integrate this with Istanbul instead of Blanket, but running into some trouble. Hopefully I'll get it working soon and if so , I'll try to fork this with an example.
I incorporated your change. If you'd like to send me a pull request with a regression test, I'd be happy to merge it.
I'm concerned that this solution only works in one direction. What if you use the real version of a module, then want to stub it. Will the module cache foil you then?
I've actually become somewhat disenchanted with using node for testing code designed to run in a browser. You wind up having to simulate a browser within Node (with jsdom, by emulating XMLHttpRequest, et.) and run into lots of node vagaries like module caches that have no analogue in the browser.
As per using require.extension, I've opened this question: http://stackoverflow.com/questions/28884377/better-way-to-require-extensions-with-node-js
We'll see if the community has anything to say about it.
As per "real then stub" @danvk is right, I've created a third spec which runs after the real module is tested. The third spec will then attempt to stub the module again in which case, the stubbing is ignored.
//spec3.js
describe('Spec 3', function() {
it('renders the HeroSection', function() {
(function(){
global.reactModulesToStub = [
'LoginStatus.jsx',
'AutocompleteSearchInput.jsx'
];
})();
var MyComponent = require('../../app/pages/FrontPage/HeroSection.jsx'),
TestContext = require('./../lib/TestContext').getRouterComponent(MyComponent),
component = TestContext.component;
console.log(global.document.body.innerHTML);
TestUtils.findRenderedDOMComponentWithClass(
component, 'HeroSection');
});
it('1 x 1', function() {
assert.equal(1, (1 * 1), '1 x 1 = 1');
});
});
Also, in my test helper, I'm emptying global.reactModulesToStub after each test.
beforeEach(function(){
global.reactModulesToStub = [];
});
At runtime, the transpiler doesn't even try to stub the LoginStatus module in spec3.js
Spec 1
stubbing /home/adjavaherian/app/modules/LoginStatus.jsx
✓ renders the HeaderNav class (253ms)
✓ 1 x 1
Spec 2
✓ renders LoginStatus
✓ 1 x 1
Spec 3
stubbing /home/adjavaherian/app/modules/AutocompleteSearchInput.jsx
1) renders the HeroSection
✓ 1 x 1
5 passing (356ms)
1 failing
@danvk I think using Node to test React components is the way to go, because they are isomorphic, so they should do the same thing on client and server. Using jsdom for simple component testing should be fine, no? Thanks.
@adjavaherian could you try moving the delete require.cache[filename];
out of the if
statement in jsx-stub-transform.js
?
re: node vs. browser, they're only isomorphic if you assume all browsers work exactly like node and jsdom :) And if you use jQuery or XHRs from any of your code being tested, things quickly get very complicated.
@adjavaherian
Using jsdom for simple component testing should be fine, no? Thanks.
imo, and I might be misunderstanding how this all fundamentally works, I would think that we should be able to test directly against the vdom itself. When you think about it, React just uses the VDom where all the elements are rendered anyway. Why use a separate dom (jsdom , etc..) when we can probably just access the vdom somehow. Not sure if this can be accessed directly though, would be cool to look into this more.
@danvk
re: node vs. browser, they're only isomorphic if you assume all browsers work exactly like node and jsdom :) And if you use jQuery or XHRs from any of your code being tested, things quickly get very complicated.
I can see your point on this but is there any decent alternatives? Do you have any recommended projects to take a look at?
@jxm262 that may be possible, but I don't believe React exposes any way to do it. Here's another project of mine which uses Mocha + PhantomJS for testing.
Sorry, been really busy last few hours. Moving the delete outside of the if doesn't change anything in the results.
// Transform a file via JSX/Harmony or stubbing.
function transform(filename) {
if (shouldStub(filename)) {
console.log('stubbing', filename);
delete require.cache[filename];
return reactStub;
} else {
var content = fs.readFileSync(filename, 'utf8');
return ReactTools.transform(content, {harmony: true});
}
delete require.cache[filename];
}
// Install the compiler.
require.extensions['.jsx'] = function(module, filename) {
return module._compile(transform(filename), filename);
};
...hmm
@jxm262, as per DOM and React DOM, Ya its there, but I think you can only access it with React-Tools. Maybe we should keep this focused on the require problem for now? Thanks.
@danvk, @jxm262 check this out: https://github.com/adjavaherian/mocha-react. I was able to do away with the transpiler in favor of Babel. Also, specs can now do mocking on demand with Mockery. I think this should work for any .js .jsx components with or without Flux, React-Router. Thanks so much for your help! Happy Friday.
@adjavaherian Thanks for sharing. I wasn't even aware of babel :)
I know, babel and mockery are awesome, but don't seem to be very well known. They work very well together.
@adjavaherian I'm following along your setup, but I am getting:
mocha tests/*.js --recursive --reporter spec
.../tests/bootstrap.js:13
global.navigator = window.navigator;
^
TypeError: Cannot read property 'navigator' of undefined
Should I just be setting the user agent to node or am I missing something?
I copied your package.json
and installed:
+ "assert": "^1.3.0",
+ "blanket": "^1.1.6",
+ "glob": "^5.0.3",
+ "jsdom": "^4.1.0",
+ "mocha": "^2.2.1",
+ "mocha-lcov-reporter": "0.0.2",
+ "mockery": "^1.4.0",
+ "react-tools": "^0.13.1",
+ "sinon": "^1.14.1",
Also what is the loader referencing to in the blanket
config? Is it still being used?
Thanks!
Edit: I resolved it by using mocha-jsdom
instead. Also you can pass babel
directly to mocha
:
mocha ./tests/*.js --recursive --reporter spec --compilers js:babel/register
@bizmurr, cool I was gonna say it sounds like a jsdom issue, navigator is not really used in our test setup, but might come in handy for some people. I don't have any issues using jsdom ~3 and Node 0.10.35 with a clean clone and npm install. I think blanket was part of @danvk 's original repo, probably something to do with travis, I don't know much about it. Nice note about babel as a compiler flag.
I'm using the compiler solution you've posted pretty much as is, it works great and stubs as expected. But, if you run two tests, one of which is stubbed and one of which is the actual test for what you may have stubbed out in previous test, then the actual component for the test has already been stubbed out globally, so the second test will fail.
For example, here's spec1 and spec2. spec1 tests the HeaderNav component, which includes the LoginStatus component. Its stubbed out properly, in the first test, but then when spec2 starts, LoginStatus is already stubbed, compiled and cached.
LoginStatus needs to be tested on its own, but stubbed out when included in HeaderNav. This is the most real world solution I have come across. So figuring it out would be huge for React testing with React-Router, Flux, etc. I didn't post LoginStatus, but its a JSX and outputs a simple component, but requires React-Router and Flux, hence the TestContext, which adds that stuff.
But the real problem is that I don't want to stub LoginStatus for my second test. I've added an afterEach to the mocha helper, but that does nothing, because using require.extensions loads all .jsx modules up front.