I will try to be brief and to the point. In our school we use Google Sheets as a class book. I have a script that counts the absences of individual students. The script is written in Google Apps Scripts (GAS), but the purpose of the application and even GAS are irrelevant to this question.
I'm currently trying to rewrite my old code and try to implement Jest unit testing. So my question will be simple. How can I mock a global variable that I call inside the function under test?
Here I paste the part of the code that introduces the global variables:
const absenceFileId = "some_string"
const teachersFileId = "some_string"
const studentGroupsId = "some_string"
const classbookFolderId = "some_string"
// STATE
let teachers = {}
let groups = {}
let subjectCounter = {}
let errors = []
let students = {}
function setupStateVariables() {
teachers = getTeachers(removeEmptyRowsAndHeader(getValuesArray(teachersFileId, 0, 1, 1, 50, 3)));
groups = getGroups(removeEmptyRowsAndHeader(getValuesArray(studentGroupsId, 0, 1, 1, 400, 6)))
subjectCounter = createSubjectCounter(removeEmptyRowsAndHeader(getValuesArray(studentGroupsId, 1, 1, 1, 200, 4)))
}
Subsequently, I need to update the global variable subjectCounter inside the function:
function updateSubjectCounter(group, schoolClass, subject) {
if (!(subject in subjectCounter[schoolClass])) {
return
}
if (group) {
subjectCounter[schoolClass][subject][group] += 1
} else {
let keys = Object.keys(subjectCounter[schoolClass][subject])
if (keys.length > 1) {
for (let key of keys) {
subjectCounter[schoolClass][subject][key] += 1
}
} else {
subjectCounter[schoolClass][subject][null] += 1
}
}
}
I have the previous code in the main.js module.
I export the function for testing as follows. If statement is here due to GAS enviroment.:
if (typeof module !== 'undefined') {
module.exports = {
"getTeachers": getTeachers,
"getGroups": getGroups,
"createSubjectCounter": createSubjectCounter,
"updateSubjectCounter": updateSubjectCounter
}
}
For testing, I use the main.test.js module, which looks like this:
const main = require('./main');
test("Test funkce updateSubjectCounter", () => {
main.updateSubjectCounter("", "a", "IFM")
expect(subjectCounter).toEqual({something})
The question is how to mock the global variable subjectCounter in this test?
EDIT: I am using local enviroment with NodeJS for testing.
Related
What is the best way of creating an object of Singletons? I have a class which will be shared between different applications. This class should be a singleton PER application.
Here is what I currently have. However, when I instantiate app1 twice, it creates a new instance for that.
class Sample {
constructor(appName) { // eslint-disable-line
if (!Sample._instance[appName]) {
Sample._instance[appName] = this
console.log('Creating new instance')
this.counter = 0
}
return Sample._instance[appName]
}
getVal () {
this.counter++
console.log('counter: ' + this.counter)
}
}
Then I call it like this:
import Sample from './sample'
const sample1 = new Sample('app1')
sample1.getVal() // OK - prints 1
sample1.getVal() // OK - prints 2
const sample1a = new Sample('app1')
sample1a.getVal() // NOK - prints 1 - should print 3
const sample2 = new Sample('app2')
sample2.getVal() // OK - prints 1
sample2.getVal() // OK - prints 2
If instead I do something like below, then how can I actually pass in appName when the instance is created already during import?
const sample = new Sample(appName)
export default sample
Just adding static _instance = {} got rid of the runtime error I encountered and made it work as you want.
I also tested this in nodejs to make sure there's not anything weird going on when importing the class instead of declaring it in the same file.
class Sample {
static _instance = {};
constructor(appName) { // eslint-disable-line
if (!Sample._instance[appName]) {
Sample._instance[appName] = this
console.log('Creating new instance')
this.counter = 0
}
return Sample._instance[appName]
}
getVal () {
this.counter++
console.log('counter: ' + this.counter)
}
}
const sample1 = new Sample('app1')
sample1.getVal() // OK - prints 1
sample1.getVal() // OK - prints 2
const sample1a = new Sample('app1')
sample1a.getVal() // NOK - prints 1 - should print 3
const sample2 = new Sample('app2')
sample2.getVal() // OK - prints 1
sample2.getVal() // OK - prints 2
Differentiate the roles of the container object and the singleton.
Below I made an IIFE singleton container that handles creating and storing new instances of Sample and it returns an existing instance if it's already stored.
You can keep the class definitions and container singleton in their own file and export the container. Since it's already invoked in the module, you just need to call container.get('app') in the importing scripts.
class Sample {
constructor() {
console.log('Creating new instance')
this.counter = 0
}
getVal () {
this.counter++
console.log('counter: ' + this.counter)
}
}
const container = (() => {
const singletons = {};
const get = (appName) =>{
if (!singletons[appName]){
singletons[appName] = new Sample();
}
return singletons[appName]
}
return { get }
})();
let sample1 = container.get('app1')
sample1.getVal();
sample1.getVal();
let sample1A = container.get('app1')
sample1A.getVal();
let sample3 = container.get('app2')
sample3.getVal();
I just looked at compiled script of my source code and I think I noticed something weird.
This ES6 code:
const data = {someProperty:1, someNamedPropery: 1, test:1};
const {
someProperty, // Just gets with 'someProperty' peoperty
someNamedPropery: changedName, // Works same way, but gets with 'changedName' property
test: changedNameWithDefault = 1, // This one, introduces new variable and then uses it to compare that to void 0
} = data;
Compiles to this:
"use strict";
const data = {someProperty:1, someNamedPropery: 1, test:1};
var someProperty = data.someProperty,
changedName = data.someNamedPropery,
_data$test = data.test,
changedNameWithDefault = _data$test === void 0 ? 1 : _data$test;
I am curious why Babel introduces new variable _data$test. Can't it just be something like this?
...
changedNameWithDefault = data.test === void 0 ? 1 : data.test;
It still works.
Notice, that new variable introduction happens only when I'm trying to assign default value if key isn't present in data variable or is undefined.
Does this affect application performance if data.test is big enough? And I wonder if Garbage Collector takes care of it (_data$test variable).
Can't it just be something like this?
No, but it's slightly subtle why not: If test is an accessor property, accessing it is a function call. The destructuring code, and Babel's translation of it, will only call the accessor once. Your change would call it twice. Here's an example:
const data = {
someProperty:1,
someNamedPropery: 1,
get test() {
console.log(" test called");
return 1;
},
};
function es2015OriginalCode() {
const {
someProperty,
someNamedPropery: changedName,
test: changedNameWithDefault = 1,
} = data;
console.log(` changedNameWithDefault = ${changedNameWithDefault}`);
}
function babelCode() {
"use strict";
var someProperty = data.someProperty,
changedName = data.someNamedPropery,
_data$test = data.test,
changedNameWithDefault = _data$test === void 0 ? 1 : _data$test;
console.log(` changedNameWithDefault = ${changedNameWithDefault}`);
}
function yourCode() {
"use strict";
var someProperty = data.someProperty,
changedName = data.someNamedPropery,
changedNameWithDefault = data.test === void 0 ? 1 : data.test;
console.log(` changedNameWithDefault = ${changedNameWithDefault}`);
}
console.log("ES2015 original code:");
es2015OriginalCode();
console.log("Babel's code:");
babelCode();
console.log("Your code:");
yourCode();
.as-console-wrapper {
max-height: 100% !important;
}
While we can tell looking at the code that test isn't an accessor property in that specific example, the code transformations don't usually work with that much context, so this is the safer general case way to translate that code.
I need a recursive method to roll up all results from a series of paginated calls and returns the complete list of results. Something feels off in the way I am doing it and feel there is a better way to do this, possibly with Array.reduce
Any recommendations appreciated.
interface Result {
users: Widget[];
start: number;
}
interface Widget {
id: number;
}
// create 3 widgets for test
const widgets = Array(3).fill(null).map((i, index: number) => {
return {
id: index + 1,
} as Widget;
});
const getFromAPI = (start: number = 0): Result => {
// return 1 at a time from a specified position
const current = widgets.slice(start, start + 1);
let nextStart: number | undefined;
if (start < widgets.length - 1) {
nextStart = start + 1;
}
return {
users: current,
start: nextStart,
}
}
// I don't like that em is outside the scope here
let em: Widget[] = [];
const getWidgets = (start?: number): Widget[] => {
const result = getFromAPI(start);
em = [...em, ...result.users];
if (result.start) {
getWidgets(result.start);
}
return em;
}
const all = getWidgets();
You don't like that em is outside the scope of getWidget() and that you are reassigning it inside the function body and thus relying on side effects. Instead it seems that you want something more purely functional, without relying on state changes.
If so, then one approach you can take is to make em another argument to the function, and have it initially empty, and then returning the recursive result of getWidgets() called with the next version of em:
const getWidgets = (em: Widget[] = [], start?: number): Widget[] => {
const result = getFromAPI(start);
const nextEm = [...em, ...result.users];
return result.start ? getWidgets(nextEm, result.start) : nextEm;
}
I've changed the reassignment of em to a new variable nextEm just in case you want to avoid side effects even within the body of getWidgets as well. It makes the algorithm a little clearer anyway.
You can verify that a call to getWidgets() yields the same result as in your example:
const all = getWidgets();
console.log(all); // [{ "id": 1}, { "id": 2}, { "id": 3}]
Playground link to code
In the code below is an iterator:
const cart = ['Product 0','Product 1','Product 2','Product 3','Product 4','Product 5','Product 6','Product 7','Product 8','Product 9','Product 10']
function createIterator(cart) {
let i = 0;//(*)
return {
nextProduct: function() {
//i:0; (**)
let end = (i >= cart.length);
let value = !end ? cart[i++] : undefined;
return {
end: end,
value: value
};
}
};
}
const it = createIterator(cart);
First I know a copy of the present state of the function's variables and the parameters are parsed.(Right?)...
And when you run
const it = createIterator(cart);
Is a property below created?
//i:0 (**);
Making it.next(); equivalent to
{
i:0;//state or value of i from createIterator() definition;
next : function(cart){
let end = (this.i >= cart.length);
let value = !end ? cart[this.i++] : undefined;
return {
end: end,
value: value
};
}
Or does state of the value of i in line (*) from the first code, Is what's what is modified?
Please if this point is not clear... let me know to explain better.
Calling the iterator will create an instance of i scoped to the createIterator function. The object returned from it will only have access to that specific instance of i, but i is not a property of the object returned. It only can access it due to the function scope.
You can see a little better how this works if we break your code down a little more simply:
function createIterator(cart, display) {
let i = 0;
return {
next: function() {
i++;
console.log(display + ' next: ', i);
}
};
}
const cart1 = [];
const cart2 = [];
const it1 = createIterator(cart1, 'cart1');
it1.next();
it1.next();
const it2 = createIterator(cart2, 'cart2');
it2.next();
it2.next();
Each instance of the iterator has a different copy of i and only the object returned from the iterator function can access it.
Okay, I realize this can be considered subjective, but I'm trying to better understand how to consider scope when writing modules that only expose what's needed publicly. I have a string utility that I've written as an object literal below:
const substrings = {
query: {},
text: "",
results: [],
exists: function (index) {
const exists = index >= 0
return exists
},
check: function () {
const q = this.query
const start = q.openIndex
const stop = q.closeIndex
if (q.hasOpen && !q.hasClose) {
console.log("Missing closing delimiter.")
}
if (!q.hasOpen && q.hasClose) {
console.log("Missing opening delimiter.")
}
if (q.hasOpen && q.hasClose && start > stop) {
console.log("Closing delimiter found before opening.")
}
if (!q.hasOpen && !q.hasClose && this.results.length == 0) {
console.log("No results found.")
}
const order = start < stop
const check = q.hasOpen && q.hasClose && order
return check
},
update: function () {
const q = this.query
const text = this.text
q.before = this.text.indexOf(q.open)
q.start = q.before + q.open.length
this.text = text.slice(q.start, text.length)
q.stop = this.text.indexOf(q.close)
q.after = q.stop + q.close.length
q.openIndex = q.before
q.closeIndex = q.before + q.stop
q.hasOpen = this.exists(q.openIndex)
q.hasClose = this.exists(q.stop)
const newPosition = q.start + q.after
q.position = q.position + newPosition
this.query = q
},
substrings: function () {
const q = this.query
const current = this.text.slice(0, q.stop)
const fullLength = this.text.length
this.text = this.text.slice(q.after, fullLength)
this.results.push(current)
this.update()
if (this.check()) {
this.substrings()
}
},
init: function (open, close, text) {
this.results = []
this.query = {
open,
close,
position: 0,
}
this.text = text
this.update()
},
getSubstrings: function (open, close, text) {
this.init(open, close, text)
if (this.check()) {
this.substrings()
return this.results
}
},
getSubstring: function (open, close, text) {
this.init(open, close, text)
if (this.check()) {
return this.text.slice(0, this.query.stop)
}
}
}
I want to use it as a Node module and expose the getSubstring and getSubstrings methods, but if I were to do:
module.exports = {
all: substrings.getSubstrings,
one: substrings.getSubstring
}
I would get an error due to the usage of this. I realize that if I replace this with the object var name substrings to reference it directly, it works. I could also refactor it to be one big function or smaller functions and just export the 2 I need.
I am trying to go about learning things the right way and am struggling with how I should be thinking about context. I understand how this changes here, but I feel like I'm not fully wrapping my head around how I should consider context when structuring my code.
Is there a more elegant solution to expose methods with code like this that wasn't written to separate private and public methods?
A simple solution would be to bind the exported functions to the proper calling context inside the exports object:
module.exports = {
all: substrings.getSubstrings.bind(substrings),
one: substrings.getSubstring.bind(substrings)
}
Personally, I prefer using the revealing module pattern over object literals for situations like this. With the revealing module pattern, create an IIFE that returns the desired functions, referring to local variables instead of properties on this. For example:
const { getSubstrings, getSubstring } = (() => {
let query = {}
let text = ''
let results = []
function exists(index) {
return index >= 0
}
function check() {
const q = query;
// ...
}
...
function getSubstrings(open, close, text) {
}
...
return { getSubstrings, getSubstring };
})();
module.exports = {
all: getSubstrings,
one: getSubstring
}
This is somewhat opinion-based, but code can be easier to read when there aren't any this references to worry about.