How do I unit test "non standalone functions"? - javascript

I have the knowledge of unit testing standalone functions like helper classes but how do I deal with functions that are not standalone, typically within a class file that has multiple validations and results?
Example below showing multiple conditions check and response in different result.
Do I call the valueCheck and proceedApiCheck function in my test case? But there're different scenarios or actions I don't need within test. (eg, setState / navigating)
Do I write a new function of valueCheck and proceedApiCheck in my test case? But this will means I'm having 2 different logic in my code. Someday if I change my logic in app, my test case will not fail since it's referring to the old logic.
Can any of you shed some light on this?
Example
export class Screen1 extends React.Component {
valueCheck = (value) => {
if(value === 'abc'){
this.setState({ isNavigating:true, transfer: true })
this.proceedApiCheck(value)
}
if(value === '123'){
this.setState({ isNavigating:true, transfer: false })
this.proceedApiCheck(value)
}
}
proceedApiCheck = async(value) =>{
let data
try{
data = await FirstApi(value);
this.setState(data)
}catch(){
this.navigateToScreen('Failure')
return;
}
switch(data.name){
case 'fake adidas':
this.navigateToScreen('Failure')
return;
case 'fake nike':
this.navigateToScreen('Failure')
return;
}
try{
const result = await secondApi(data.price);
switch(result.currency){
case 'EURO':
this.navigateToScreen('Euro')
case 'Pound':
this.navigateToScreen('Pound')
default:
this.navigateToScreen('Dollar')
}
}catch(){
this.navigateToScreen('Failure')
return;
}
}
}

You've made a valuable discovery:
The most straightforward way to write code is not necessarily the most robust way to write code.
This unfortunate problem is exacerbated by the way most people use classes: the implicit this reference makes it very easy to do too much in one method, let alone one class. And that has led you to a second valuable discovery:
One of the most valuable things about isolated unit tests is they give you feedback on your design.
Here the difficulty isolating the test is telling you that you've coupled separate concerns in a way that's difficult to tease apart. You've got a method with seven (7!!) different exit points, and now you're stuck because that's a lot of mocking to try to trigger the appropriate logic to make sure you hit all of them.
Consider the following alternative:
const FAIL = {}; // could also use Symbol() here, any unique ref
const BAD_NAMES = ['fake whatever'];
async function apiCall1() {
const resp = await fetch(someURL);
return resp.json();
}
function validate1(data) {
return data?.name === undefined || BAD_NAMES.includes(data?.name)
? FAIL
: data.name;
}
// You can imagine what validation and fetching look like for
// the second API call
function processData(data) {
switch(data.currency){
case 'EURO':
return 'Euro';
case 'Pound':
return 'Pound';
default:
return 'Dollar';
}
}
async function doTheThing() { // use a better name IRL
try {
const first = await apiCall1();
const data = validate1(first);
if (data === FAIL) throw new Error('whatever');
const second = await apiCall2(data.whatever);
const data2 = validate2(second);
if (data2 === FAIL) throw new Error('something else');
// process data we now know is good.
return processData(data);
} catch (err) {
console.error(err);
return 'Failure';
}
}
class Screen1 extends React.Component {
async proceedApiCheck () {
const nextScreen = await doTheThing();
this.navigateToScreen(nextScreen);
}
}
Here we return the failure screen trigger in one place only. The functions all have clear exits.
There is a convenience function that unboxes the request (or hides the details of an xhr if that's how you roll).
All of the validation and business logic is independently testable. It's also in one place instead of spread on various places in a lengthy method.
All of the logic is in plain functions that return values, no mocks needed other than for fetch.
The only thing that proceedApiCheck does is get the value from the logic function and navigate to the correct screen, and is simple to mock for a test.
You could, if you're really in to classes, also make all those functions static methods of your class if that's how you roll, but the important thing is that almost none of your tests need complicated mocks, and it should be much more obvious what the code paths are and how to test them.
The high-falutin' software engineering practice you want to adhere to (as pointed out by #DrewReese in the comments) is the Single Responsibility Principle which states that functions/methods should only do one logical operation. A validator only validates, a conditional only does dispatch, a function that gets data from an external source should only get the data (rather than act on it), etc.

if you think it is necessary to do unit test for valueCheck (maybe it is a complicated function in real case), move it outside the class and make it testable alone. Then it should be something like:
function valueCheck(value) {
switch(value) {
case 'abc':
return {shouldProceedApiCheck: true, newState:{ isNavigating:true, transfer: true }}
case '123':
return {shouldProceedApiCheck: true, { isNavigating:true, transfer: false }}
default:
return {shouldProceedApiCheck: false, newState: {} }
}
.....
class Screen1 extends React.Component {
....
whenToCall = ()=>{
const {newState, shouldProceedApiCheck} = valueCheck(value)
this.setState(valueCheck(newState), ()=>{
if(shouldProceedApiCheck) {
this.proceedApiCheck(value)
}
})
}
....
}
Further reading: https://medium.com/front-end-weekly/making-testable-javascript-code-2a71afba5120

Related

Detect when svelte store is not used anymore

I'm making a custom svelte store by wrapping around a svelte writable store.
I want to detect when that store is not subscribed by any component; when the subscription count is 0
My objective is to clear some heavy external resources (websockets) that were tied to the custom store when no one is using it.
Currently, I'm counting the subscriptions and unsubscriptions by wrapping around subscribe( ) method. It works as expected. But It looks like a nasty hack to me.
My question: Is there a standard / clean way to achieve this behavior in Svelte?
If not, can someone with more experience in Javascipt and svelte confirm whether this is legit?
Demo on : https://svelte.dev/repl/f4e24fb5c56f457a94bf9cf645955b9f?version=3.43.1
import { writable } from 'svelte/store';
// Instanciate the store
export let store = MakeStore();
// By design, I want a function that returns a custom svelte store
export function MakeStore(initialValue = null) {
const { subscribe, set, update } = writable(initialValue);
let subscribercount = 0;
let wsubscribe = function (run, callback) {
subscribercount++;
console.log("subscribercount++", subscribercount);
let wunsubscribe = subscribe(run, callback);
return () => {
subscribercount--;
console.log("subscribercount--", subscribercount);
if (subscribercount == 0) {
// -------------------------------
// Free up resources
// I want a clean way to get here
// -------------------------------
console.log("Cleaning up...");
}
return wunsubscribe();
}
}
// Some external calls here
let store = {
subscribe: wsubscribe,
set: newvalue => {
set(newvalue);
// Some external calls here
},
update: update
};
// Some external calls here
return store;
}
Yes, it's built into the store and documented here
from the docs
If a function is passed as the second argument, it will be called when the number of subscribers goes from zero to one (but not from one to two, etc). That function will be passed a set function which changes the value of the store. It must return a stop function that is called when the subscriber count goes from one to zero.
so you would do for example:
const count = writable(0, () => {
console.log('got a subscriber');
return () => console.log('no more subscribers');
});
Update 8 Feb 2023
Note that the above works for both readable and writable stores, in the case of derived stores where you would have the following code:
const count = derived(items, ($items, set) => {
console.log('got a subscriber to a derived store');
return () => console.log('no more subscribers to derived store');
});
Here it will log no more subscribers to derived store, when either the number of subscribers drops to 0 or when the original store changes (this is because this entire function ($items, set) => {...} runs again).
As of v3.55.1 there is no built in way to circumvent this.

Returning Boolean from Cypress Page Object

I'm trying to understand how do i return a boolean from cypress page object?
Use case:
I am trying to check if an element is present on the page. If so,
return a boolean.
Code:
class DataTable {
constructor() {
return this;
}
has(fieldName) {
// HOW DO I RETURN A BOOLEAN HERE?
return cy.get('.view-and-field-name').contains(fieldName)
}
}
Mainpage.spec.js
const dataTable = new DataTable();
expect(dataTable.has(field.fieldName)).to.be.true;
I'd appreciate some insights on what I am doing wrong.
Thank you for the time.
According to this post, and according to cypress suggestions for custom commands and page objects I suggest the next custom command:
Cypress.Commands.add("validateIfElementExistsInDom", (selector) => {
cy.get('body')
.then($body => {
expect($body.find(selector).length > 0).to.be.true
})
})
And you can use it in the test like :
cy.validateIfElementExistsInDom(fieldName)
And a general return boolean function example:
Cypress.Commands.add("validateIfElementExistsInDomAsBoolean", (selector) => {
return cy.get('body')
.then($body => {
return cy.wrap($body.find(selector).length > 0) //Cy wrap is needed so the function is chainable
})
})
//in test code
cy.validateIfElementExistsInDomAsBoolean(fieldName)
.then(boolean => expect(boolean).to.be.true)
The expect() on Mainpage has already been executed internally in cy.get('.view-and-field-name').contains(fieldName), so it is redundant.
If dataTable.has(field.fieldName) fails, the test stops there - it will never pass out a false value.
If you want has(fieldName) to pass out a boolean, you need to switch to jquery inside, e.g
has(fieldName) {
const jquerySelector = `.view-and-field-name:contains(${fieldName})`;
return Cypress.$(jquerySelector);
}
But it is much simpler to go with Cypress commands and use
const dataTable = new DataTable();
dataTable.has(field.fieldName);
Personally, I'd ditch the page object and just use the command directly in the test.
cy.get is not a synchronous function due to the retry-ability strategy,
so there is no way to return Boolean directly.
If necessary, you can use variant of should with callback.
In your case, a simpler variant with exist is suitable (or be.visible, possibly more accurate for your case):
class DataTable {
has(fieldName) {
return cy.get('.view-and-field-name').contains(fieldName);
}
}
const dataTable = new DataTable();
dataTable.getField(field.fieldName).should('exist');

Assign and Query Javascript Arrow Function for Metadata

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+.

Is it a good idea to overcharge a method?

I have 3 classes, all extend the previous one.
Entity -> Body -> Player
Each one has a die() method which do very different things.
Entity.die() will call the db
Body.die() will animate the body
Player.die() will call the UI and play special sound.
I don't want to manually call Entity.die() inside Body.die method, mainly because I have many classes and many common methods and I don't want to forget something.
I wrote this little piece of code which does exactly this, the Error stack is easy to understand and points to the correct lines.
function overLoadMethods (parent, children) {
const methods = {}
for (let [fname, fn] of Object.entries(parent)) {
if (typeof fn === 'function') {
if (children[fname]) {
methods[fname] = function () {
fn()
children[fname]()
}
Object.defineProperty(methods[fname], 'name', { value: fname })
} else {
methods[fname] = fn
}
}
}
return methods
}
function createEntity () {
return {
die: () => {
console.log(new Error().stack)
console.log('entity die')
}
}
}
const bodyMethods = {
die: () => {
console.log(new Error().stack)
console.log('body die')
}
}
function createBody () {
const entity = createEntity()
const overLoadedMethods = overLoadMethods(entity, bodyMethods)
return {
...entity,
...bodyMethods,
...overLoadedMethods
}
}
const playerMethods = {
die: () => {
console.log(new Error().stack)
console.log('player die')
}
}
function createPlayer () {
const body = createBody()
const overLoadedMethods = overLoadMethods(body, playerMethods)
return {
...body,
...playerMethods,
...overLoadedMethods
}
}
const player = createPlayer()
// will call Entity.die() then Body.die() then Player.die()
player.die()
Everything is working fine but I never saw this pattern before and I guess there is a good reason which I'm unaware of.
Could someone point the weakness of this pattern if there is one (pretty sure there is) ?
Common Lisp has something similar. When you define a method in a derived class you can decide whether this method should be executed:
:before (i.e. the base method will be called automatically after specialized one)
:after (i.e. the base method will be called automatically before the specialized one)
:around (i.e. only the specialized method will be called, but inside its body you can call the base method with call-next-method that is a special syntax that allows calling base method with either the parameters specified by the caller or the parameters that you want to pass instead).
For example C++ only has around available for general methods (but without the ability to call the base version with original parameters) and forces instead use of before in constructor and after in destructors.
I understand the desire to not repeat code and create code that makes it hard to make mistakes and forget things. But you still have code the you need to remember to wire up. For example, instead of calling Entity.die() you need to call overLoadMethods(). I'm not sure that's an improvement over regular of classes and calling super.die().
You can get the chained method behavior using ES6 classes (you can also get it using prototypes). This has a lot of advantages:
• The pattern is baked into the language.
• It's very clear to see parent/child relationship
• There's a lot of commentary, theory, and examples of different patterns
class Entity {
die() {
// Entity-specific behavior
console.log('entity die')
}
}
class Body extends Entity {
die() {
super.die()
// Body-specific behavior
console.log('body die')
}
}
class Player extends Body {
die() {
super.die()
// Player-specific behavior
console.log('player die')
}
}
const player = new Player
// will call Entity.die() then Body.die() then Player.die()
player.die()

Passed-in Values Not Available at Run-time of Function in Angular App

I realize there is something I'm missing in terms of how and specifically when the products of certain functions are available in JavaScript.
In my Angular app, in order to get a user's initials, I am parsing data being returned from the API, and retrieving the first letter of the firstName, as well as the first letter of lastName in two different functions. These two functions are working as expected, and I can see the correct results in the console:
getFirstNameFirstLetter() {
if (this.authenticationService.isAuthenticated()) {
const userObj = JSON.parse(sessionStorage.getItem('currentUser'));
const userInfo = userObj.data;
const firstName = userInfo.name.first;
const firstNameFirstLetter = firstName.trim().charAt(0);
console.log(firstNameFirstLetter);
return firstNameFirstLetter;
}
}
getLastNameFirstLetter() {
if (this.authenticationService.isAuthenticated()) {
const userObj = JSON.parse(sessionStorage.getItem('currentUser'));
const userInfo = userObj.data;
const lastName = userInfo.name.last;
const lastNameFirstLetter = lastName.trim().charAt(0);
console.log(lastNameFirstLetter);
return lastNameFirstLetter;
}
}
Now comes the part I'm not fully understanding. When I then pass the returned values of these two functions, in order to get the initials, like this:
getInitials(firstNameFirstLetter, lastNameFirstLetter) {
if (this.authenticationService.isAuthenticated()) {
if (!this.firstNameFirstLetter || !this.lastNameFirstLetter) {
console.log('Names not ready!');
return;
} else if (this.firstNameFirstLetter && this.lastNameFirstLetter) {
console.log(firstNameFirstLetter + lastNameFirstLetter);
return firstNameFirstLetter + lastNameFirstLetter;
}
}
}
... I get "Names not ready!" printed to the console each time.
By the way, I am running these functions within Angular's ngOnInit life cycle hook, like this:
ngOnInit() {
this.getFirstNameFirstLetter();
this.getLastNameFirstLetter();
this.getInitials(this.firstNameFirstLetter, this.lastNameFirstLetter);
}
I know this has something to do with what's available when, because I get 'undefined' when I use break points and debug the two values being passed into the "getInitials()" function. In other words, the function doesn't have access to the returned values of the other two functions at the time it's run -- hence I'm getting 'Names not ready!' printed to the console. My question is, what am I missing, architecturally, to resolve this kind of issue?
So what is happening here is that JavaScript doesn't think you are using the return values for getFirstNameFirstLetter and getLastNameFirstLetter, so when it makes the call, instead of waiting for that call to finish, it goes on to the next one, which introduces a race condition. if you simply change it to
ngOnInit() {
let temp1 = this.getFirstNameFirstLetter();
let temp2 = this.getLastNameFirstLetter();
this.getInitials(this.firstNameFirstLetter, this.lastNameFirstLetter);
}
then it will wait for the previous functions to finish before calling the next.
Also, I don't use const very often, so I could be wrong and it could follow different scope rules, but by normal scope rules, setting a variable in that function, it is only available in that function, you would need to set it as
this.firstNameFirstLetter = firstName.trim().charAt(0);
to have access to it outside the function.
Or, so as to kill two birds with one stone, you could do
ngOnInit() {
this.firstNameFirstLetter = this.getFirstNameFirstLetter();
this.lastNameFirstLetter = this.getLastNameFirstLetter();
this.getInitials(this.firstNameFirstLetter, this.lastNameFirstLetter);
}
or
ngOnInit() {
let firstNameFirstLetter = this.getFirstNameFirstLetter();
let lastNameFirstLetter = this.getLastNameFirstLetter();
this.getInitials(firstNameFirstLetter, lastNameFirstLetter);
}
depending on if you need the variables again or just for that function.

Categories

Resources