I'm trying to run an express app in ES6. I'm using the following workflow:
Transpile ES6 to ES5 using the following gulp task (with "es2015" and "stage-0" presets in .babelrc):
import gulp from 'gulp';
import gulpBabel from 'gulp-babel';
import sourcemaps from 'gulp-sourcemaps';
gulp.task('babel', () => {
gulp.src([
'someClass.js',
'app.js'
], {base: './', dot: false})
.pipe(sourcemaps.init())
.pipe(gulpBabel())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('./dist'));
});
Which seems to be working fine.
Run node dist/app.js.
The following code is in someClass.js:
export default class SomeClass {
someMethod() {
return 1 + 1;
}
}
Finally, the following code is in app.js:
import SomeClass from './someClass';
//express config
console.log(SomeClass);
console.log(SomeClass.someMethod);
Which logs:
[Function: SomeClass]
undefined
Here is the relevant transpiled code:
dist/app.js
var _someClass = require('./someClass');
var _someClass2 = _interopRequireDefault(_someClass);
console.log(_someClass2.default);
console.log(_someClass2.default.someMethod);
dist/someClass.js
var SomeClass = function () {
function SomeClass() {
_classCallCheck(this, SomeClass);
}
_createClass(SomeClass, [{
key: "someMethod",
value: function someMethod() {
return 1 + 1;
}
}]);
return SomeClass;
}();
exports.default = SomeClass;
Why is someMethod undefined?
Because someMethod is a instance method. You need to instantiate the class with new to use the method.
const something = new SomeClass();
something.someMethod();
If you want to use the method without instantiating the class, you can define it as a static method.
export default class SomeClass {
static someMethod() {
return 1 + 1;
}
}
SomeClass.someMethod();
In the comment above, you said that you want to use it as a callback. To use it as a callback, you may want to bind a context to the method if you use this keyword in the method. Otherwise, the this keyword doesn't point to the instance when it's called as a callback function.
var something = new SomeClass();
element.addEventListener('click', something.someMethod.bind(something));
// or
element.addEventListener('click', (event) => something.someMethod(event));
Related
I have the following ES6 module from a Chromecast receiver that I would like to test using Mocha...
// SUT - app.js
import { Queue } from 'queue.js'
export const app = async () => {
const context = cast.framework.CastReceiverContext.getInstance();
const options = {};
options.queue = new Queue();
context.start(options);
}
This code runs inside a Chromecast user agent which provides access to the global cast object. This cast object exposes an api which in turn enables the JS thread to interact directly with the Chromecast CAF SDK. This cast variable is always available at run time.
The Queue class is slightly unusual because in order for it to work according to the CAF framework documentation, is must extend an abstract class from the framework cast.framework.QueueBase...
// queue.js
class Queue extends cast.framework.QueueBase {
initialize(){
// build queue here
}
}
Now I would like to write some unit tests to check my app function is correct. For example:
// app.test.js
import { app } from 'app.js';
it('should do some stuff', async function () {
// inject a mock cast object
global.cast = {
framework: {
QueueBase: class {},
CastReceiverContext: {
getInstance: () => {},
},
},
};
await app();
// Make some assertions
});
However, even though I am injecting a mock using global.cast, which is sufficient for all regular references to the cast object, in the case where a class is extending the injected cast object, apparently it is not yet available and I receive the following error:
ReferenceError: cast is not defined
I found an ugly hack to make this error disappear. If I place the following snippet above the class declaration then I can inject the mock at runtime and it not only works for Mocha but also for execution on the Chromecast device....
try {
// The following line throws in Mocha's node environment
// but executes fine on the Chromecast device
if (cast) {
}
} catch {
global.cast = {
framework: {
QueueBase: class {},
},
};
}
export class Queue extends cast.framework.QueueBase {
...
However, I would like to find a better solution so that I don't have to pollute my production code with this hack which is only there to allow me to run tests.
My .mocharc.yml file looks like this:
require:
- '#babel/register'
- 'ignore-styles'
- 'jsdom-global/register'
- 'babel-polyfill'
... and my command to run tests is:
mocha --recursive --use_strict
finally, my .babelrc file looks like this:
{
"presets": [
[
"#babel/preset-env"
]
],
"plugins": [
"inline-svg",
"import-graphql"
]
}
Static imports are always evaluated first, so the order of operations is roughly:
import { Queue } from 'queue.js'
class Queue extends cast.framework.QueueBase { // ReferenceError outside Chromecast!
initialize(){
// build queue here
}
}
global.cast = {
framework: {
QueueBase: class {},
CastReceiverContext: {
getInstance: () => {},
},
},
};
You can see that the mock is created after the reference to cast in app.js.
The only reliable way to run the mock creation before importing the app module is using a dynamic import:
// app.test.js
it('should do some stuff', async function () {
// inject a mock cast object
global.cast = {
framework: {
QueueBase: class {},
CastReceiverContext: {
getInstance: () => {},
},
},
};
const { app } = await import('app.js');
await app();
// Make some assertions
delete global.cast;
});
If you prefer not to repeat the mock creation and the import in every test, you can move both out of the test definition:
// app.test.js
// inject a mock cast object
global.cast = {
framework: {
QueueBase: class {},
CastReceiverContext: {
getInstance: () => {},
},
},
};
const { app } = await import('app.js');
it('should do some stuff', async function () {
await app();
// Make some assertions
});
// Optionally clean up the mock after all tests
after(() => delete global.cast);
I'm trying to import a package that I've written into another package that I've written.
Pre-Babel Loader
class TestClass {
constructor() {
// Load flags on import here
console.log("TESTING CONSTRUCTOR");
}
log(message) {
console.log("TESTING LOG");
}
}
export default new TestClass();
Post Babel Loader
var TestClass = function () {
function TestClass() {
_classCallCheck(this, TestClass);
// Load flags on import here
console.log("TESTING CONSTRUCTOR");
}
_createClass(TestClass, [{
key: "log",
value: function log(message) {
console.log("TESTING LOG");
}
}]);
return TestClass;
}();
exports.default = new TestClass();
The import itself is simply a import TestClass from 'testclass-js'. However, every single time I'm trying to load it I get a "Darklaunch is not defined" error, and can't call any of the methods of the class.
I'm wondering what I've done wrong here.
If you're trying to import the ES5/commonjs version, you'll need to import 'yourmodule'.default; This change came in around babel 6
Babel 6 changes how it exports default
Read more on the original issue: https://github.com/babel/babel/issues/2212
If your package contains both an es5 and es6+ version, you can point to the es6 version in the module key in package.json and webpack/rollup will pick that up and bundle it instead of the commonjs version
I am using ES6, and I want to start testing using mocha & chai.
My current test file code is :
const assert = require('chai').assert;
var app = require('../../../../src/app/login/loginController').default;
describe('login Controller tests', function(){
it('no idea ', function(){
let result = app();
assert.equal(result, 'hello');
})
})
and my loginController.js is :
class LoginController {
checkout(){
return 'hello';
}
}
export default LoginController
I want to import the 'checkout' function into a variable inside my test file, but so far I am able to import only the class.
Will appreciate any help, thanks !
You cannot import methods directly from classes. If you want to import a function without a class as intermediary, then you need to define the function outside the class. Or if you really meant checkout to be an instance method, then you need to call it on an instance.
Here's an example file derived from yours:
export class LoginController {
// Satic function
static moo() {
return "I'm mooing";
}
// Instance method
checkout() {
return "hello";
}
}
// A standalone function.
export function something() {
return "This is something!";
}
And a test file that exercises all functions, adapted from the file you show in your question:
const assert = require('chai').assert;
// Short of using something to preprocess import statements during
// testing... use destructuring.
const { LoginController, something } = require('./loginController');
describe('login Controller tests', function(){
it('checkout', function(){
// It not make sense to call it without ``new``.
let result = new LoginController();
// You get an instance method from an instance.
assert.equal(result.checkout(), 'hello');
});
it('moo', function(){
// You get the static function from the class.
assert.equal(LoginController.moo(), 'I\'m mooing');
});
it('something', function(){
// Something is exported directly by the module
assert.equal(something(), 'This is something!');
});
});
I have file foo.js:
export function bar (m) {
console.log(m);
}
And another file that uses foo.js, cap.js:
import { bar } from 'foo';
export default m => {
// Some logic that I need to test
bar(m);
}
I have test.js:
import cap from 'cap'
describe('cap', () => {
it('should bar', () => {
cap('some');
});
});
Somehow I need override implementation of bar(m) in test. Is there any way to do this?
P.S. I use babel, webpack and mocha.
Ouch.. I found solution, so I use sinon to stub and import * as foo from 'foo' to get object with all exported functions so I can stub them.
import sinon from 'sinon';
import cap from 'cap';
import * as foo from 'foo';
sinon.stub(foo, 'bar', m => {
console.log('confirm', m);
});
describe('cap', () => {
it('should bar', () => {
cap('some');
});
});
You can replace/rewrite/stub exports only from within the module itself. (Here's an explanation)
If you rewrite 'foo.js' like this:
var bar = function bar (m) {
console.log(m);
};
export {bar}
export function stub($stub) {
bar = $stub;
}
You can then override it in your test like this:
import cap from 'cap'
import {stub} from 'foo'
describe('cap', () => {
it('should bar', () => {
stub(() => console.log('stubbed'));
cap('some'); // will output 'stubbed' in the console instead of 'some'
});
});
I've created a Babel plugin that transforms all the exports automatically so that they can be stubbed: https://github.com/asapach/babel-plugin-rewire-exports
While #Mike solution would work in old versions of sinon, it has been removed since sinon 3.0.0.
Now instead of:
sinon.stub(obj, "meth", fn);
you should do:
stub(obj, 'meth').callsFake(fn)
Example of mocking google oauth api:
import google from 'googleapis';
const oauth2Stub = sinon.stub();
sinon.stub(google, 'oauth2').callsFake(oauth2Stub);
oauth2Stub.withArgs('v2').returns({
tokeninfo: (accessToken, params, callback) => {
callback(null, { email: 'poo#bar.com' }); // callback with expected result
}
});
You can use babel-plugin-rewire (npm install --save-dev babel-plugin-rewire)
And then in test.js use the __Rewire__ function on the imported module to replace the function in that module:
// test.js
import sinon from 'sinon'
import cap from 'cap'
describe('cap', () => {
it('should bar', () => {
const barStub = sinon.stub().returns(42);
cap.__Rewire__('bar', barStub); // <-- Magic happens here
cap('some');
expect(barStub.calledOnce).to.be.true;
});
});
Be sure to add rewire to your babel plugins in .babelrc:
// .babelrc
{
"presets": [
"es2015"
],
"plugins": [],
"env": {
"test": {
"plugins": [
"rewire"
]
}
}
}
Lastly, as you can see the babel-plugin-rewire plugin is only enabled in the test environment, so you should call you test runner with the BABEL_ENV environment variable set to test (which you're probably doing already):
env BABEL_ENV=test mocha --compilers js:babel-core/register test-example.js
Note: I couldn't get babel-plugin-rewire-exports to work.
This was definitely a gotcha for me too...
I created a little util to workaround this limitation of sinon. (Available in js too).
// mockable.ts 👇
import sinon from 'sinon'
export function mockable<T extends unknown[], Ret>(fn: (...fnArgs: T) => Ret) {
let mock: sinon.SinonStub<T, Ret> | undefined
const wrapper = (...args: T) => {
if (mock) return mock(...args)
return fn(...args)
}
const restore = () => {
mock = undefined
}
wrapper.mock = (customMock?: sinon.SinonStub<T, Ret>) => {
mock = customMock || sinon.stub()
return Object.assign(mock, { restore })
}
wrapper.restore = restore
return wrapper
}
If you paste the above snippet into your project you can use it like so
foo.js
import { mockable } from './mockable'
// we now need to wrap the function we wish to mock
export const foo = mockable((x) => {
console.log(x)
})
main.js
import { foo } from './foo'
export const main = () => {
foo('asdf') // use as normal
}
test.js
import { foo } from './foo'
import { main } from './main'
// mock the function - optionally pass in your own mock
const mock = foo.mock()
// test the function
main()
console.assert(mock.calledOnceWith('asdf'), 'not called')
// restore the function
stub.restore()
The benefit of this approach is that you don't have to remember to always import the function in a certain way. import { foo } from './foo' works just as well as import * as foo from './foo'. Automatic imports will likely just work in your IDE.
I am new to typescript (and javascript for that matter). I put together a tiny test to see if I can do the following based on reading up on AMD, looking at JS based requires and looking at the TS doc.
Two namespaces Foo & Test
Import the test namespace
Create an instance definition
Create a singleton
Test.ts
export class TestClass {
constructor() {
}
testMethod(): void {
}
}
export function Singleton(): void {
}
Foo.ts
import Test = require('Test');
export class Bar {
private instance = new Test.TestClass();
private singleton = Test.Singleton;
constructor() {
this.instance.testMethod();
this.singleton();
}
}
Generated JS
// Foo.js
define(["require", "exports", 'Test'], function(require, exports, __Test__) {
var Test = __Test__;
var Bar = (function () {
function Bar() {
this.instance = new Test.TestClass();
this.singleton = Test.Singleton;
this.instance.testMethod();
this.singleton();
}
return Bar;
})();
exports.Bar = Bar;
});
// Test.js
define(["require", "exports"], function(require, exports) {
var TestClass = (function () {
function TestClass() {
}
TestClass.prototype.testMethod = function () {
};
return TestClass;
})();
exports.TestClass = TestClass;
function Singleton() {
}
exports.Singleton = Singleton;
});
Based on my code above, is my list of what I wanted to do correct? If so YAY, if not I think I may have missunderstood something about AMD :(
Your understanding of AMD is immaculate. However your understanding of the singleton pattern is incorrect. Have a look at this for an example : http://www.codebelt.com/typescript/typescript-singleton-pattern/