Using proxyquire in a browserify factor bundle - javascript

Stuck with this one.
I am using laravel elxir with tsify to generate my js. I run the typescript through factor-bundle to split common js modules into a seperate files. I don't think though that will be a problem in this case because everything is in a spec.js
spec.ts
/// <reference path="../../../typings/index.d.ts" />
import "jasmine-jquery";
// #start widgets
import "./widgets/common/widget-factory/test";
factory-widget/index.ts
export class WidgetFactory {
.... this contains a require call to browser.service which i need to mock
}
factory-widget/test.ts
...
import {WidgetFactory} from "./index";
const proxyRequire = require("proxyquire");
it("should output the factory items", ()=> {
proxyRequire('./widgets/browser.service/index',{
"#global": true,
});
}
browser-service.ts
...
export class BrowserService implements IBrowserService{
//details
}
Getting an error Uncaught TypeError: require.resolve is not a function on line 262.
Here is the code ( yeah it's over 20,000 lines ) how else are you supposed to debug this stuff . ¯_(ツ)_/¯
I've looked at Stubbing with proxyquire. I am not holding my breath getting an answer on this one.
Edit: 06-09-2016
Proxquire is needed to overide the require call in the boot method of the WidgetFactory class
In factory-widget/index.ts:
boot(output = true):any {
let required = {};
if (this._sorted.length) {
this._sorted.forEach((key)=> {
if (output) {
console.log(`${this._path}${key}/index`);
// this is where is need to overide the call to require.
required[key] = require(`${this._path}${key}/index`);
}
});
this._sorted.forEach((key)=> {
let dependencies = {},
module = this._factory[key];
if (module.hasOwnProperty(this.dependencyKey)) {
module[this.dependencyKey].map((key)=> {
dependencies[_.camelCase(key)] = this.isService(module) ? new required[key] : key;
});
}
if (this.isTag(module)) {
if (output) {
document.addEventListener("DOMContentLoaded", ()=> {
riot.mount(key, dependencies);
});
}
//console.log(key,dependencies);
}
else {
}
})
}
}

I've added a proxyquireify example to the tsify GitHub repo. It's based on the simple example in the proxyquireify README.md.
The significant parts are the re-definition of require to call proxyquire in foo-spec.ts:
const proxyquire = require('proxyquireify')(require);
require = function (name) {
const stubs = {
'./bar': {
kinder: function () { return 'schokolade'; },
wunder: function () { return 'wirklich wunderbar'; }
}
};
return proxyquire(name, stubs);
} as NodeRequire;
and the configuration of the proxyquire plugin in build.js:
browserify()
.plugin(tsify)
.plugin(proxyquire.plugin)
.require(require.resolve('./src/foo-spec.ts'), { entry: true })
.bundle()
.pipe(process.stdout);
If you build the bundle.js and run it under Node.js, you should see that the message written to the console includes strings returned by the functions in the stubbed ./bar module.

Related

custom global function not defined when testing in Jest; works fine when not testing

I have a custom, globally-scoped function in my Express app, foo. When running my Jest test scripts, this function is caught as undefined. Thus, any tests using them fail.
index.d.ts:
declare global{
function foo(): string;
}
export {};
src/Utils/index.ts:
global.foo = function foo(){
return "bar";
};
src/Modules/Example.module.ts:
export const test = () => {
// This will return bar, as expected, when developing.
// A reference error will only be thrown when running npm test.
return foo();
};
src/Modules/Example.test.ts:
import { test } from "./Example.module";
describe("modules/example", () => {
describe("test", () => {
it("returns bar", () => {
let bar = test();
expect(bar).toBe("bar");
});
});
});
Despite this not being an issue while developing, this test results in the error:
ReferenceError: foo is not defined.
export const test = () => {
return foo();
^
...
};
You can specify src/Utils/index.ts as a setup file, which Jest will load and execute before running tests. You can add it to your Jest configuration file (or create one if you don't have one):
Assuming a CJS-format Jest configuration, jest.config.js:
module.exports = {
// Your other configuration options
"setupFiles": ["<rootDir>/src/Utils/index.ts"]
};
It will look slightly different if you are using a JSON or TypeScript Jest configuration file.
However I don't recommend using global variables (even if you use them a lot). With a proper code editor setup, it is easy to import a function from another file.

Mocha finds global variable is undefined when extended by class

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);

Cypress POM approach | How to read fixtures data in POM_Page.js file in cypress

I am new to cypress and trying to create framework on POM.
I have followed these steps for creating framework
Created Object repository with file named 'locators.json', data in that file looks like
this
{
"RxClaims_LoginPage":
{
"UserName":"#f0",
"Password":"#f1",
"SignInButton":"#xlg0003"
}
}
Under Integration>Examples I have created tests named OR_Approch.js which look like
/// <reference types="Cypress" />
import ORLoginPage from '../PageObjects/OR_Login'
describe('OR_Approach',function(){
it('RxClaimsLogin', function() {
const login = new ORLoginPage();
cy.visit('/')
cy.wait(2000)
login.EnterData_In_UserName()
login.Password()
login.SignInButton()
})
})
3.And I have created one other folder under Integration>POM which consists all the POMs - one of them named OR_Login.js looks like
class ORLoginPage{
EnterData_In_UserName()
{
cy.fixture('example').then(function (dataJson) {
this.testData = dataJson;
})
cy.fixture('locators').then(function (oRdata) {
this.objRep = oRdata;
})
cy.enterDatainTextBox(this.objRep.RxClaims_LoginPage.UserName,this.testData.UserName)
return this
}
Password(){
return 'cy.enterDatainTextBox(this.objRep.RxClaims_LoginPage.Password,this.testData.Password)'
}
SignInButton(){
return 'cy.clickOnObject(this.objRep.RxClaims_LoginPage.SignInButton)'
}
}
export default ORLoginPage;
Under Support commands.js consists custom methods which looks like this
Cypress.Commands.add('enterDatainTextBox', (textBoxElePath, textValue) => {
cy.get(textBoxElePath).type(textValue)
})
So my question is I want to access locators.js data for all the functions in OR_Login.js. I have tried beforeEach method for test files which works fine but when i use it in any class like OR_Login.js it does not work. Please suggest some way so data for fixtures can be read in POM class files.
The beforeEach() way will not work in OR_Login.js because it is not a test file, and mocha is not executing that file.
Instead, you can simply import the JSON file as a variable to OR_Login.js and use that variable.
// OR_Login.js
const locators = require('./path/to/locators.json');
class ORLoginPage {
...
Password(){
return cy.enterDatainTextBox(locators.RxClaims_LoginPage.Password,locators.Password)
}
...
}
If you have dynamic data that will change based on the class, you could also create your class with a constructor that would point it to the correct data in the locators.json file.
All of that being said, Cypress strongly urges you not to use POM with Cypress. If you do choose to continue using the POM with Cypress, I'd highly recommend that all of your functions you execute in a test are contained within the class that executes the code (instead of mixing cy. and your class in the test), as well as look into how you can better chain your command off of one another, so that they are executed in the same cypress command chain.
The problem with using POM with Cypress, it works against the Mocha hooks like beforeEach() which are very useful for readable tests.
But classes have constructors which might be used to fill your this.testData.
class ORLoginPage{
constructor() {
cy.fixture('example').then(function (dataJson) {
this.testData = dataJson;
})
cy.fixture('locators').then(function (oRdata) {
this.objRep = oRdata;
})
}
EnterData_In_UserName() {
cy.enterDatainTextBox(this.objRep.RxClaims_LoginPage.UserName, this.testData.UserName)
return this
}
Password() {
...
}
SignInButton(){
...
}
}
export default ORLoginPage;
The cy.fixture() is async because it reads a file, so it may be better to use an init() method.
This will be useful for any async commands you have in the POM.
class ORLoginPage{
init() {
return new Promise((resolve, reject) => {
cy.fixture('example').then(function (exampleData) {
this.testData = exampleData
cy.fixture('locators').then(function (locatorData) {
this.objRep = locatorData;
resolve(true)
})
})
}
}
...
/// <reference types="Cypress" />
import ORLoginPage from '../PageObjects/OR_Login'
describe('OR_Approach',function(){
it('RxClaimsLogin', async function() { // add async to the test
const login = new ORLoginPage();
await login.init()
cy.visit('/')
cy.wait(2000)
login.EnterData_In_UserName()
login.Password()
await login.SignInButton() // may be useful to await other async methods
})
})

Mocking named imports and constructors ES6 & Ava

I have a class constructor, with a function I want to stub:
class Service {
constructor(){}
async someFunction() {
try {
// does stuff
}
catch (e) {}
}
}
In the file I want to test, this is imported an used like so:
const { Service } = require('something')
const newService = new Service('xyz')
I'm struggling to get this to import & stub correctly in my tests.
Currently am importing like this:
t.context.service = {
Service: class Service {
constructor () {
this.someFunction = sinon.stub()
}
}
}
This import seems to work, but then I can't get a reference back to it through the constructed version. Any help on this one?
I want to be able to make an assertion like:
t.true(t.context.service.Service.someFunction.calledOnce)
AVA doesn't provide any stubbing. Have a look at https://github.com/testdouble/testdouble.js/ or http://sinonjs.org/.

How to stub exported function in ES6?

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.

Categories

Resources