So, I'm working my way through learning Jest, and in a current Aurelia project, the internal working of the generated main.js script imports a configuration object (environment). Note this code is all as-generated.
// main.js
import environment from './environment';
import {PLATFORM} from 'aurelia-pal';
import 'babel-polyfill';
import * as Bluebird from 'bluebird';
// remove out if you don't want a Promise polyfill (remove also from webpack.config.js)
Bluebird.config({ warnings: { wForgottenReturn: false } });
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.feature(PLATFORM.moduleName('resources/index'));
// Uncomment the line below to enable animation.
// aurelia.use.plugin(PLATFORM.moduleName('aurelia-animator-css'));
// if the css animator is enabled, add swap-order="after" to all router-view elements
// Anyone wanting to use HTMLImports to load views, will need to install the following plugin.
// aurelia.use.plugin(PLATFORM.moduleName('aurelia-html-import-template-loader'));
if (environment.debug) {
aurelia.use.developmentLogging();
}
if (environment.testing) {
aurelia.use.plugin(PLATFORM.moduleName('aurelia-testing'));
}
return aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName('app')));
}
The environment object is just holding a couple simple values:
export default {
debug: true,
testing: true
};
Now, when I want to test the branching logic in main.js, I want to be able to flip those booleans to ensure they do or don't execute the config changes as appropriate:
import {configure} from '../../src/main';
import environment from '../../src/environment';
/* later... */
describe('when the environment is not set to debug', () => {
environment.debug = false;
it('should not configure development logging', () => {
configure(aureliaMock);
expect(aureliaMock.use.developmentLogging.mock.calls.length).toBe(0);
});
});
This does not work, as the version of environment being checked inside the configure() function still has the values in the source module. I recognize that environment in this case is my local value, but what I don't know is how to affect the instance of environment that's being checked.
I tried using the jest.mock() syntax you'd use with an ES6 class constructor, but that doesn't work either. I will probably change the configure() signature to accept an environment for testing, but before doing so I wanted to see if there's a way to do this via mocks first.
Related
I am creating a NodeJS and Express app and want a module for configuration that runs once on start up then makes a serverConfig object available to any other module that needs these values. So far I have something like this:
const loadConfig = () => {
// logic to load configuration from environment variables, AWS Secrets Manager, etc.
}
export default loadConfig()
Now I can do
import serverConfig from 'config'
When I do this in multiple files will loadConfig() be called each time? Or will I have access to a reference to a single object from all of my imports? I only want this loadConfig() function to execute once on start up. These config values should not change during execution. And I don't want to repeat the (perhaps) time consuming logic in loadConfig() multiple times during each request to my API.
If not, what is the correct way to call loadConfig() once on server start up and create an object that all other modules can import and reference.
Everything in file scope executes once. Once the exports of a file are resolved, they are directly used by reference for any subsequent imports.
Any function call that is is not inside function itself will be executed exactly once when that file is imported when your program first starts up.
// a.ts
console.log('foo')
// b.ts
import './a'
// c.ts
import './a'
// main.ts
import './b'
import './c'
If you compile and run main.ts you will get exactly one output of "foo" to the console.
Given that, your example could be rewritten as:
const loadConfig = () => {
// logic to load configuration from environment variables, AWS Secrets Manager, etc.
}
const config = loadConfig()
export default config
Now its pretty clear that loadConfig() is called from the file scope and so only executes once.
This version:
export default loadConfig()
just avoids the intermediary variable but otherwise functions exactly the same.
If you did want to run that logic multiple times, you just need to export a function to do it for you.
// config.ts
export const loadConfig = () => {
// ...
}
// main.ts
import { loadConfig } from './config'
loadConfig()
loadConfig() // now do it again.
That doesn't sound like what you want, but I list it here to make it clear what the difference is.
I have an external library that is exported as a function, in the Stub documentation it only accepts an input with the first parameter as object and the second parameter as method , so how could I stub a library that is exported as a function in a Node ES Modules environment (without Commonjs)?
(In my specific case, I had used a library that use the internet to work, and I wanted to test derivated functions without accessing the internet, so I want to stub the external function library)
Attempts:
I couldn't use solutions like proxyquire as it is a solution based on require and module cache deletion, which are not supported within Node's ES modules.
I don't want to use proxyquire-universal because it simulates the operation of require in normal ES, and it's just a function in the whole project that I wanted to test, I was looking for a simpler solution
Changing the import mode doesn't work as it's not recompiled like in babel, so even if I import as import * as obj from 'lib' only the function name is changed
I had this error environment with 3 files:
An external library called "sum" for example, which I don't want to change, exported as follows:
External Library: externalSum.js
module.exports = function sum(a, b){
console.log(">>>>> running without stub <<<<<")
return a + b
}
This library used in the middle of a project file called mathProblems
Internal File: mathProblems.js
import sum from 'externalSum'
export function sumMore1(a) {
return sum(a, 1);
}
And I have a test file
Internal File: spec.js
import sinon from 'sinon'
import assert from 'assert'
import sumObj from 'externalSum'
import { sumMore1 } from '../mathProblems.js'
describe('sumMore1 is working', () => {
it('sumMore1 test', () => {
const sum_stub = sinon.stub(sumObj) // NOT STUBBING
sum_stub.withArgs(1, 1).returns(2) // NOT STUBBING
const result = sumMore1(1)
assert.strictEqual(result, 2)
});
});
I didn't find this solution anywhere on the internet, i found some solutions that work for node with request or babilon, but not for my case using ES Modules:
https://github.com/sinonjs/sinon/issues/562
https://minaluke.medium.com/how-to-stub-spy-a-default-exported-function-a2dc1b580a6b
So I wanted to register the solution in case anyone needs it.
To solve this, create a new file, which can be allocated anywhere in the project, in this case I will call it sumImport.js:
Internal File: sumImport.js
import sum from 'externalSum';
// export as object
export default {
sum
}
The object needs to be called inside the created function I want to test, and changed the import way:
Internal File: mathProblems.js
import sumObj from './sumImport.js';
export function sumMore1(a) {
const { sum } = sumObj;
return sum(a, 1);
}
And I finally managed to import as an object in the test:
Internal File: spec.js
import sinon from 'sinon'
import assert from 'assert'
import sumObj from '../sumImport.js'
import { sumMore1 } from '../mathProblems.js'
describe('sumMore1 is working', () => {
it('sumMore1 test', () => {
const sum_stub = sinon.stub(sumObj, "sum") // STUBBED
sum_stub.withArgs(1, 1).returns(2) // STUBBED
const result = sumMore1(1)
assert.strictEqual(result, 2)
});
});
I hope it helps someone and if someone else has some better solutions I would also be grateful!
I'm trying to figure out how to use the browser-based aws-sdk.js with require.js when building a web app.
If I try to build a class and include aws-sdk in the require.js define, once I try to create an instance and reference the AWS class, it says it is undefined.
For example, I have a function that checks to see if the credentials are an instance of AWS.Credentials and if not, it tries to initialize it with an STS token it retrieves via Rest.
The problem is, the code dies on the if(this.credentials instanceof AWS.Credentials) saying AWS is undefined. It even dies on my simple check of the needsRefresh.
This is what my define looks like - I'll include the 'needsRefresh' wrapper for an example of the sort of thing that is throwing the Undefined error:
define(['require','aws-sdk','jquery'],
function (require, AWS, $) {
// Aws helper class
function AwsHelper() { };
AwsHelper.prototype = {
credentials: null,
tokenNeedsRefresh: function () {
//////////////////////////////////////////////////////////////////
// errors out on the following line with: //
// TypeError: Cannot read property 'Credentials' of undefined //
//////////////////////////////////////////////////////////////////
if(this.credentials instanceof AWS.Credentials) {
return this.credentials.needsRefresh();
} else return true;
}
};
return AwsHelper;
}
);
I also tried the following format at the top of the file:
define(function (require) {
var AWS = require("aws-sdk"),
$ = require("jquery");
/* .. */
If I remove all onLoad references to the refresh code running, it will load and I can create an instance. But as soon as I call any function that references the AWS class, it dies.
How do I make sure that AWS class definition is still in global space once an instance is spawned?
Dunno what difference the paths will make (it's finding and loading the code just fine - I can see AWS Class in namespace in the debugger as it's loading but it's not in the namespace on the function call), but added on request:
requirejs.config({
baseUrl: '/js',
paths: {
lib: 'lib',
ImageUploader: 'ImageUploader'
}
});
I decided to try to play with this again and seemed to have figured out how I was going about it incorrectly. I have yet to integrate it back into my existing code setup, but I think I have a working solution.
The thing I was doing wrong was I was trying to set up the AWS class so I could load it as a module (thus why I tried wrapping it as the require.js suggested).
Playing with it this time around, I noticed something I hadn't before. I had an AWS that was undefined in the local scope and an other AWS that held the class definition in the global scope. So it was trying to specify AWS in my define that was creating the local 'null' version:
define(
["jquery","aws-sdk","dropzone","app/MyUtilities"],
function ($, AWS, Dropzone, MyUtilities) {
"use strict";
function MyClass() {};
return MyClass;
}
);
jquery and dropzone have whatever is needed to make sure they load that way, but aws-sdk seems to do some of it's own asynchronous loading. Thus the scope variable in the function is undefined.
Technically, it seems the only thing I needed defined as a module in the function wrapper is my own utilities module. So by switching it to:
define(
["app/MyUtilities","jquery","aws-sdk","dropzone"],
function (MyUtilities) {
"use strict";
function MyClass() {};
return MyClass;
}
);
... it seems $ and jquery are defined as needed, Dropzone is defined as needed and AWS is defined as needed. (and I don't get my errors when accessing the static AWS.config or AWS.Credentials definitions as I did before)
I need to enable some global variables to be reachable for my test so I am setting up a Custom Environment to be used in the testEnvironment option in my jest.config.json to achieve that.
For our project we have a TypeScript file that we use for setupFilesAfterEnv option and that works just fine, however the testEnvironment seems to support only ES5. Is there any way to use TypeScript in such option?
I successfully created a Custom Jest Environment using ES5 syntax, however since we are injecting global variables I need TypeScript to also declare a global namespace see: https://stackoverflow.com/a/42304473/4655076.
{
///...
setupFilesAfterEnv: ['<rootDir>/test/setup.ts'], // This works with ts
testEnvironment: '<rootDir>/test/customJestEnvironment.ts', // This doesn't work with ts
}
You might find this helpful: Configure Jest global tests setup with .ts file (TypeScript)
But basically you can only pass in compiled JS files as environments.
You can do what that article suggests. But it didn't work for me out of the box. So I manually compile my env.
i.e.
in package.json
"test": "tsc --lib es6 --target es6 --skipLibCheck -m commonjs --esModuleInterop true path/to/env.ts &&
jest --config=jest.config.js",
And in jest.config.js
{
testEnvironment: '<rootDir>/path/to/env.js', // Note JS extension.
}
I solved this by using ts-node and the following command:
node -r ts-node/register ./node_modules/jest/bin/jest.js
This essentially compiles the typescript on-the-fly, so that jest receives the emitted javascript, without the need of actually compiling your typescript sources to js.
You will need to enable esModuleInterop TS compiler option for this to work properly.
TestEnvironment.ts
import NodeEnvironment from 'jest-environment-node';
import type {Config} from '#jest/types';
class TestEnvironment extends NodeEnvironment {
constructor(config: Config.ProjectConfig) {
super(config);
// this.testPath = context.testPath;
// this.docblockPragmas = context.docblockPragmas;
}
public async setup(): Promise<void> {
await super.setup();
console.log('SETTING UP...');
// await someSetupTasks(this.testPath);
// this.global.someGlobalObject = createGlobalObject();
// // Will trigger if docblock contains #my-custom-pragma my-pragma-value
// if (this.docblockPragmas['my-custom-pragma'] === 'my-pragma-value') {
// // ...
// }
}
public async teardown(): Promise<void> {
await super.teardown();
console.log('TEARING DOWN!');
// this.global.someGlobalObject = destroyGlobalObject();
// await someTeardownTasks();
}
}
export default TestEnvironment;
This solution however, will break globalSetup -- if you use jest-ts.
As you might know, typescript files are just superset to javascript to provide strong type checking. Jest's engine/runtime however expects your files in CommonJS format javascript files.
You can have a separate tsconfig.env.json just for this env.ts. Compile this before running jest test and use the compiled env.js in your jest.config.js.
tsc -p tsconfig.env.json && jest
Also i have never seen people writing configuration files in TS.
why CommonJS ? because jest is essentially running on top of node. node supports Javascript files in CommonJS format. Node has started supporting es modules as well recently! This is a big thing!
You can create a global.d.ts file at the root of your project.
Then you can define global variables as seen below. In my case, it was a NestJS application, but you can define anything.
declare global {
namespace NodeJS {
interface Global {
app: INestApplication;
}
}
}
This is another example for client project where we define window properties like innerWidth;
declare namespace NodeJS {
interface Global {
innerWidth: number;
dispatchEvent: Function;
}
}
Inside your .d.ts definition file:
type MyGlobalFunctionType = (name: string) => void
Add members to the browser's window context:
interface Window {
myGlobalFunction: MyGlobalFunctionType
}
Same for NodeJS:
declare module NodeJS {
interface Global {
myGlobalFunction: MyGlobalFunctionType
}
}
Now you declare the root variable
declare const myGlobalFunction: MyGlobalFunctionType;
Then in a regular .ts file, but imported as side-effect, you actually implement it:
global/* or window */.myGlobalFunction = function (name: string) {
console.log("Hey !", name);
};
And finally use it elsewhere :
global/* or window */.myGlobalFunction("Ayush");
myGlobalFunction("Ayush");
I've been following the pattern for setting up TypeScript, RequireJS, and Jasmine that Steve Fenton describes here:
https://www.stevefenton.co.uk/Content/Blog/Date/201407/Blog/Combining-TypeScript-Jasmine-And-AMD-With-RequireJS/
That pattern as really worked well and truly unblocked me (yay!), but I'm now at the point where I need to customize some settings for RequireJS but I can't seem to figure out where to put my require.config call. Everywhere I've tried has caused breaks and regressions. Here are the two approaches that seem most logical/promising
In SpecRunner.cshtml
<script data-main="/Scripts/TypeScript/RequireJsConfig" src="/Scripts/require.js"></script>
In RequireJsConfig.ts
require.config({
baseUrl: "../Scripts",
paths: {
jquery: "../jquery-2.1.3"
}
});
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Attempt 1: When I try it this way I immediately get this error
//
// JavaScript runtime error: Object doesn't support property or method 'config'
//
import TestLoader = require("Tests/TestLoader");
TestLoader.Run();
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Attempt 2: When I try it this way, everything builds and runs without errors, but
// Jasmine doesn't find any of the tests. All I get is "No specs found" even
// though I see the breakpoints on my "it" statements getting hit.
//
require(["Tests/TestLoader"], (testLoader) => {
testLoader.Run();
});
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
jasmine.getEnv().execute();
In TestLoader.ts
import GuidHelperTests = require("Tests/T3/Helpers/GuidHelperTests");
import ObjectHelperTests = require("Tests/T3/Helpers/ObjectHelperTests");
class TestLoader {
public static Run: () => void = () => {
GuidHelperTests.Run();
ObjectHelperTests.Run();
}
}
export var Run = () => TestLoader.Run();
In GuidHelperTests.ts
import T3 = require("T3/T3Lib");
export var Run = () => {
describe("GuidHelper tests", () => {
it("GUID validator validates good GUID", () => {
// etc. ...
My guess is that Attempt 2 doesn't work because of some kind of sequencing issue where the test discovery process is happening before modules are loaded, or something like that. I'm just not versed enough in RequireJS to know what my options are here.
I prefer to keep my configuration away from my application - you can pre-register the configuration like this, and it will be picked up by RequireJS when it loads. No need to add it to your first file.
<script>
var require = {
baseUrl: "../Scripts",
paths: {
jquery: "../jquery-2.1.3"
}
};
</script>
<script data-main="/Scripts/TypeScript/RequireJsConfig" src="/Scripts/require.js"></script>