I've been thinking about how to test particular functions used by other functions within the same file.
For example, say I have two functions within one file:
// validators.js
export const required = ...;
export const containsUppercase = ...;
export const containsNumber = ...;
export const password = (val) => [required, containsUppercase, containsNumber].every(f => f(val));
So clearly, the password validator depends on the required, containsUppercase and containsNumber functions. But what if I wanted to stub those out, so my tests didn't actually have to care about those functions or how they validate.
This is a simple, silly example, but I've ran into the same problem with more complex scenarios.
For example, if I try to do this with sinon, I might try doing this:
import * as validators from './validators';
sinon.stub(validators, 'required').returns(false);
sinon.stub(validators, 'containsUppercase').returns(true);
sinon.stub(validators, 'containsNumber').returns(true);
// test
expect(validators.password('notImportant')).toBe(false);
I tried doing this, but I couldn't get it to work. I found that I could get it to work, if I did something like this, though I haven't tested it.
const required = ...;
const containsUppercase = ...;
const containsNumber = ...;
const password = (val) => [ex.required, ex.containsUppercase, ex.containsNumber].every(f => f(val));
export default ex = {
required,
containsUppercase,
containsNumber,
password,
}
This solution is also ugly, since I now I'd have to worry about exporting the function in the right place, and also reference it through the export object.
Is there a cleaner way to do this? Or rather, what would be the best way to test these kinds of functions?
The pattern in the second snippet is correct. password should refer to object methods in order for them to be mocked outside of validators.js scope.
The way to achieve this behaviour naturally in JS is using this:
export class Validators {
required = (...) => { ... }
...
password = (val) => [this.required, ...].every(...);
}
export default new Validators;
Or, with normal (non-arrow) functions:
export default {
required(...) {
...
}
...
password(val) {
[this.required, ...].every(...);
}
}
In the second case required, etc. methods should be additionally bound if they use this when being called like [this.required, ...].every(...).
"but having to call password(requiredFn, uppercaseFn, numberFn, val) every time doesn't seem very appealing to me."
You can use default values for parameters:
const defaultValidators = [required, containsUppercase, containsNumber];
const password = (value, validators = defaultValidators) => validators.every(f => f(value));
That allows you to call both password('pass') and password('pass', [v => false]) and thereby stub your validation functions.
Related
I have the following:
export const ObjC = Codec.struct({
name: Codec.string,
value: Codec.number,
})
export type ObjType = Codec.TypeOf<typeof ObjC>
I want a function for decoding this object and returning an Error (not DecoderError). Similar to:
import { fold } from 'fp-ts/lib/Either'
import { pipe } from 'fp-ts/lib/function'
const decodeObj = async (str: string): Promise<Either<Error, ObjType>> => {
return pipe(
ObjC.decode(str),
fold(err => toError(err), m => m), // This doesn't do want I want
)
}
How can I return the right types for this function in an idiomatic way?
Just going off of the return type you've listed, it sounds to me like you want to perform a transformation to the left value when the thing is Left and leave the code unchanged when the value is Right. In that case the mapLeft helper function is exactly what you're looking for.
This can be achieved by saying:
import { mapLeft } from 'fp-ts/lib/Either';
// -- rest of your code --
const decodeObj = async (str: string): Promise<Either<Error, ObjType>> => {
return pipe(
objCodec.decode(str),
mapLeft(toError),
);
};
However, I have some questions. For one the code as written will never succeed to parse because the input string will never match an object. I'm guessing there are other piece of logic you've omitted, but as it stands the code looks a bit wrong.
The problem is rather simple. We need to imbue a function with a parameter, and then simply extract that parameter from the body of the function. I'll present the outline in typescript...
abstract class Puzzle {
abstract assign(param, fn): any;
abstract getAssignedValue(): any;
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
Let's set the scene:
Assume strict mode, no arguments or callee. Should work reasonably well on the recent-ish version of v8.
The function passed to assign() must be an anonymous arrow function that doesn't take any parameters.
... and it's alsoasync. The assigned value could just be stored somewhere for the duration of the invocation, but because the function is async and can have awaits, you can't rely on the value keeping through multiple interleaved invocations.
this.getAssignedValue() takes no parameters, returning whatever we assigned with the assign() method.
Would be great to find a more elegant solution that those I've presented below.
Edit
Okay, we seem to have found a good solid solution inspired by zone.js. The same type of problem is solved there, and the solution is to override the meaning of some system-level primitives, such as SetTimeout and Promise. The only headache above was the async statement, which meant that the body of the function could be effectively reordered. Asyncs are ultimately triggered by promises, so you'll have to override your Promise with something that is context aware. It's quite involved, and because my use case is outside of browser or even node, I won't bore you with details. For most people hitting this kind of problem - just use zone.js.
Hacky Solution 2
class HackySolution2 extends Puzzle {
assign(param: any, fn: AnyFunction): AnyFunction {
const sub = Object(this);
sub["getAssignedValue"] = () => param;
return function () { return eval(fn.toString()); }.call(sub);
}
getAssignedValue() {
return undefined;
}
}
In this solution, I'm making an object that overrides the getAssignedValue() method, and re-evaluates the source code of the passed function, effectively changing the meaning of this. Still not quite production grade...
Edit.
Oops, this breaks closures.
I don't know typescript so possibly this isn't useful, but what about something like:
const build_assign_hooks = () => {
let assignment;
const get_value = () => assignment;
const assign = (param, fn) => {
assignment = param;
return fn;
}
return [assign, get_value];
};
class Puzzle {
constructor() {
const [assign, getAssignedValue] = build_assign_hooks();
this.assign = assign;
this.getAssignedValue = getAssignedValue;
}
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
const puzzle = new Puzzle();
puzzle.test();
Hacky Solution 1
We actually have a working implementation. It's such a painful hack, but proves that this should be possible. Somehow. Maybe there's even a super simple solution that I'm missing just because I've been staring at this for too long.
class HackySolution extends Puzzle {
private readonly repo = {};
assign(param: any, fn) {
// code is a random field for repo. It must also be a valid JS fn name.
const code = 'd' + Math.floor(Math.random() * 1000001);
// Store the parameter with this code.
this.repo[code] = param;
// Create a function that has code as part of the name.
const name = `FN_TOKEN_${code}_END_TOKEN`;
const wrapper = new Function(`return function ${name}(){ return this(); }`)();
// Proceed with normal invocation, sending fn as the this argument.
return () => wrapper.call(fn);
}
getAssignedValue() {
// Comb through the stack trace for our FN_TOKEN / END_TOKEN pair, and extract the code.
const regex = /FN_TOKEN_(.*)_END_TOKEN/gm;
const code = regexGetFirstGroup(regex, new Error().stack);
return this.repo[code];
}
}
So the idea in our solution is to examine the stack trace of the new Error().stack, and wrap something we can extract as a token, which in turn we'll put into a repo. Hacky? Very hacky.
Notes
Testing shows that this is actually quite workable, but requires a more modern execution environment than we have - i.e. ES2017+.
I found this awesome article on how to create method chaining in nodejs es5
https://dev.to/nedsoft/method-chaining-in-javascript-3klb
But at a scale, we might wanna decompose our method chaining implementation in multiple files. I can't figure out how to do that.
I found the solution which works as below.
// Validator.js
const Type = require('./type.validator')
const _ = require('lodash')
const proto = _.merge({}, Type)
const Validator = () => {
return _.create(proto)
}
module.exports = Validator
// type.validator.js
module.exports = {
isString () {
console.log('isString')
return this
},
isEmail () {
console.log('isEmail')
return this
}
}
// Use case
const Validator = require('./abc')
Validator().isString().isEmail()
This solution not just demonstrates the decomposition of the chaining method but the user will also get a clean autocomplete and return type.
A special thanks to #bergi for all his helpful comments above.
I wanted to know if it was possible by combining webpack and js' oop to arrive at a functional code like the one presented below.
The goal is to be able to isolate each of the elements of my site (sidebar, main,...) in different files while making sure that they can interact between.
Is this possible with webpack and pure js or not?
import ApplePicker from "./my_path/applePicker.js";
import NiceFarmer from "./my_path/niceFarmer.js";
const orchard = function () {
const appleNumber = 10;
const jack = new ApplePicker();
const daniel = new NiceFarmer();
jack.eatAnApple();
daniel.eatAnApple();
// appleNumber have to be now === 2
}
// Example of applePicker.js structure
const ApplePicker = function () {
this.eatAnApple = function () {
// Do something
}
}
export default ApplePicker;
yes you can.
If you simply want to consume a variable from applePicker.js:
// applePicker.js
export const apples = 10
If you want to be able to reassign that variable you might want to add a simple facade layer on top of that:
// applePicker.js
let apple = 5
export function getApple() {
return apple;
}
export function setApple(newValue) {
apple = newValue;
}
Even better, if you want other functions to "fire" when a variable is changed, use the Observer pattern
I'm struggling with using spyOn as part of testing my utils.js module. I've tried various methods and approaches but all seem to yield the "expected mock function to have been called". For the record, other unit tests work OK, so there shouldn't be any issue with my actual test setup.
Below is a simplified test case with two functions and one test, and I can't even get these to work. Did I misunderstand the spyOn altogether?
// utils.js
function capitalHelper(string){
return string.toUpperCase();
}
function getCapitalName(inputString){
return capitalHelper(inputString.charAt(0)) + inputString.slice(1);
}
exports.capitalHelper = capitalHelper
exports.getCapitalName = getCapitalName
// utils.test.js
const Utils = require('./utils');
test('helper function was called', () => {
const capitalHelperSpy = jest.spyOn(Utils, 'capitalHelper');
const newString = Utils.getCapitalName('john');
expect(Utils.capitalHelper).toHaveBeenCalled();
})
I do ont use spyOn(), but jest.fn() instead for all mock scenario
In your case I would do the following
test('helper function was called', () => {
Utils.capitalHelper = jest.fn((s) => Utils.capitalHelper(s))
const newString = Utils.getCapitalName('john')
expect(Utils.capitalHelper.mock.calls.length).toBe(1)
})
First line could have simply be :
Utils.capitalHelper = jest.fn()
since you don't seem to be testing the returned value in your test :)
You can find more details on jest.fn() on the jest official documentation : https://facebook.github.io/jest/docs/en/mock-functions.html
----------------------- EDIT
I got it : the problem occurs because within your utils.js file, getCapitalName uses the defined function, not the one pointed by the export.
To be able to mock the function in use you could change your utils.js file to
// utils.js
const Utils = {
capitalHelper: string => string.toUpperCase(),
getCapitalName: inputString => Utils.capitalHelper(inputString.charAt(0)) + inputString.slice(1)
}
export default Utils
then the tests I gave before will work