Sinon stub withArgs ignores extra arguments - testing

My production code looks like:
exports.convertWord = number => { /* some logic here */ }
exports.methodUnderTest = () => {
return exports.convertWord(1);
}
Test code:
const mockConvertToWord = sinon.stub();
mockConvertToWord.withArgs(1).returns('one');
fileUnderTest.convertWord = mockConvertToWord;
const result = fileUnderTest.methodUnderTest();
expect(result).toBeEqual('one');
Test above is green. I expect my test will break if I change prod code to this:
exports.convertWord = number => { /* some logic here */ }
exports.methodUnderTest = () => {
return exports.convertWord(1, 'another arg');
}
but it's not. Sinon works fine even when I pass extra params which I didn't point in withArgs method. How can I tell sinon to return value only when method has been called with exact number of params?

stub
One way to do this is to use stub.callsFake(fakeFunction):
mockConvertToWord.callsFake((...args) => args.length === 1 && args[0] === 1 ? 'one' : undefined);
An alternative approach with a stub is to use a sinon.assert to make sure the stub was called with the epected arguments as noted by #deerawan.
mock
Another approach is to use a mock:
const mock = sinon.mock(fileUnderTest);
mock.expects('convertWord').withExactArgs(1).returns("one");
const result = fileUnderTest.methodUnderTest();
expect(result).toBeEqual('one');
mock.verify();

Another alternative, perhaps you can try to check the call of convertToWord like
...
expect(result).toBeEqual('one');
// check the function
sinon.assert.alwaysCalledWithExactly(mockConvertToWord, '1');
Ref:
https://sinonjs.org/releases/v6.3.4/assertions/#sinonassertalwayscalledwithexactlyspy-arg1-arg2-
Hope it helps

Related

How to save just part of URL in Cypress (without domain name)

I'm writing CY test and was trying to solve it by myself for couple of hours but unsuccessfully. Could you please help me here a bit :)
Each time I run the test I'm getting new URL, e.g.
https://website.com/en/info/is/here/
And I need to save only
/en/info/is/here/ (so without domain name)
I need to compare it later with another href.
Could you please advise me the way how to do it or at least the direction? Thanks a lot!
The cy.location() command gives you named parts, so from the example pathname is the part you need
cy.visit('http://localhost:8000/app/index.html?q=dan#/users/123/edit')
cy.location().should((loc) => {
..
cy.wrap(loc.pathname).as('url1')
...
})
If you have search or hash as well
cy.visit('http://localhost:8000/app/index.html?q=dan#/users/123/edit')
cy.location().should((loc) => {
..
cy.wrap(loc.pathname + loc.search + loc.hash).as('url1')
...
})
You can use .split() on the URL string.
Where you save it depends on the location of it's use.
Inside one test:
let pathname
cy.url().then((url) => url.split('/').slice(3)).as('pathname1')
...
cy.get('#pathname1').then(pathname1 => {
expect(pathname1).to.eq(pathname2)
})
Between tests:
let pathname1
it('gets first pathname', () => {
cy.url().then((url) => pathname1 = url.split('/').slice(3))
})
it('uses first pathname', () => {
expect(pathname1).to.eq(pathname2)
})
Use the URL interface to parse your string (also used by cy.location)
const urlString = 'https://website.com/en/info/is/here/'
const url = new URL(urlString)
const pathname = url.pathname // yields "/en/info/is/here/"
You can use the following :
let firstUrl = null;
let secondUrl = null;
cy.url().then(url => {
firstUrl = url;
});
/* sometimes later */
cy.url().then(url => {
secondUrl = url;
});
/* sometimes later */
expect(firstUrl).to.equal(secondUrl)
If you want to just compare some part of these URL I recommend you using a regex expressions.
You can use the javascript split to do this:
let partUrl
cy.url().then((url) => {
partUrl = url.split('com')[1] //saves /en/info/is/here/
})
You could use .replace() and the Cypress baseUrl value, and then store that value in a Cypress environment variable.
cy.url().then((url) => {
Cypress.env('someUrl', url.replace(Cypress.config('baseUrl'), '');
}).then(() => {
cy.log(Cypress.env('someUrl'));
})

Mocking a return value for a Subject - Unit Testing with Jasmine

I'm unit testing and part of the testing has a Subject. I'm new to Subjects and get the general gist of how they work but am struggling to mock a return value on one. I've tried various ways in the hopes of stumbling on the correct way like using a spy and returnvalue to return the number 3.
In the component:
....
private searchEvent: Subject<string> = new Subject<string>();
....
this.searchEvent.pipe(debounceTime(500)).subscribe(value => {
if (value.length >= 3) {
this.retrieveAssets(value);
}
})
....
In my spec file I basically have:
component['searchStockEvent'].subscribe(x=> of(3));
fixture.whenStable().then(() => {
expect(component['retrieveAssets']).toHaveBeenCalled();
});
searchEvent being private will make it difficult to call next directly on the subject so you have to find a way of what makes searchEvent emit a value of greater than 3 and go that route.
For this demo, we will make it public:
....
public searchEvent: Subject<string> = new Subject<string>();
....
this.searchEvent.pipe(debounceTime(500)).subscribe(value => {
if (value.length >= 3) {
this.retrieveAssets(value);
}
})
....
import { fakeAsync, tick } from '#angular/core/testing';
it('should call retrieve assets', fakeAsync(() => {
component.searchEvent.next(3);
// we need to pass 501ms in a fake way
tick(501);
expect(component.retreiveAssets).toHaveBeenCalled();
}));

Using async await on promise in svelte (#await) is not returning the desired data that is formatted in a later function call

I am currently working with a API that does not return JSON. To get around this, I take the response and push it to a array ( while formatting it to remove any indentation and split each number in the response ). I then use this array of 183 numbers and run a for loop against an array with 183 characters to generate an object ( with custom key value pairs ) from the response.
Where things get confusing is when I start to use the data in my HTML. Usually you can just say <p>{data.overallRank}</p> but I am getting the error that the object is undefined. This makes sense because the data = {} was not created until the function ran.
After searching for a solution, I cam across svelte await blocks. You can read on them here and look at the tutorial : https://svelte.dev/tutorial/await-blocks
After trying to implement this feature, I have the following code.
let playerStats = []
let proxy = "https://cors-anywhere.herokuapp.com/"
let url = proxy + "https://secure.runescape.com/m=hiscore_oldschool/index_lite.ws?player=Hess"
const data = {};
let promise = getPlayer();
async function getPlayer() {
return await fetch(url).then((response) => response.text())
.then((data) => {
return data;
});
}
getPlayer().then((playerData) => {
// format data
playerStats.push(playerData.replace(/\n/ig, ",").split(','));
console.log(playerStats);
// Begin object generation
// names array shortened
let names = ["overallRank", "overallLvl", "overallXP", "attRank", ]
const data = {};
for (var i = 0; i < playerStats[0].length; i++) {
data[names[i]] = playerStats[0][i];
}
console.log(data);
});
<main>
{#await promise}
<p>Search for a Player...</p>
{:then data}
<p>The data is {data}</p>
{/await}
</main>
I suggest throwing this code in a svelte editor which you can find here: https://svelte.dev/tutorial/await-blocks
The issue with this code is that it is printing out the data from the return data, which returns the unformatted data and not the object.
I want to return the object that is created after the second function getplayer().then()... so I can use that object throughout my HTML.
I hope I explained things well and thank you in advance for any help.
It is returning the formatted data because that what is returned by the promise function. In order to get the formatted data, you have to add the formatting to the chain of promise
async function getPlayer() {
return await fetch(url)
.then((response) => response.text())
.then((playerData) => {
// here your transformation
// do not forget to actually return something
return data;
});
You were actually very close to sorting it out, just a bit of confusion regarding how promises work I believe.
All you need to do is format your data within the block where the data is handled following the fetch & decode operations:
async function getPlayer() {
return await fetch(url)
.then((response) => response.text())
.then((data) => {
return formatData(data);
});
}
Your formatData() function is essentially there already, you just need minor changes in your code:
function formatData(playerData) {
playerStats.push(playerData.replace(/\n/ig, ",").split(','));
console.log(playerStats);
// Begin object generation
// names array shortened
let names = ["overallRank", "overallLvl", "overallXP", "attRank", ]
const data = {};
for (var i = 0; i < playerStats[0].length; i++) {
data[names[i]] = playerStats[0][i];
}
console.log(data);
return data;
}
Finally, you do not need to explicitly declare a promise to use it in an {#await} block, you know getPlayer() returns a promise, so you can directly use that instead:
<main>
{#await getPlayer()}
<p>Search for a Player...</p>
{:then data}
<p>Overall Rank: {data.overallRank}</p>
{/await}
</main>
See functioning REPL

Fetching data as reaction to observable array change in MobX

Suppose we have an observable main object array, and observable data about that array (e.g. suppose we have selectedReports and reportParameters) . Now suppose we emit action to either add report to the array or remove report from that array. How do we run an action to fetch the data for reportParameters, as reaction?
Thus far, my attempt, which isn't working, looks like this:
// report parameters stuff
async fetchAllReportParameters() {
reaction(
() => this.selectedReports,
async (reports) => {
// reset the report parameters
this.reportParameters = {}
// fetch the parameters for all the reports
await reports
.forEach((report) => {
this.fetchReportParameters(report.Id)
})
}
)
}
/**
* fetches report parameters for a reportId
* #param {number} reportId
*/
fetchReportParameters = (reportId) => {
this.reportParameters[reportId] = []
const onSuccess = (reportParameters) => {
this.reportParameters[reportId] = reportParameters
}
this.api.GetReportParameters(reportId)
.then(onSuccess, this.fetchReportParametersError)
}
fetchReportParametersError = (error) => {
// TODO: output some error here
}
Are you ever actually calling fetchAllReportParameters? If you don't, the reaction will never be created. You may instead like to create the reaction from the constructor, assuming you always want it to be run. One example:
class SomeStore {
constructor() {
this.disposeReportsReaction = reaction(
() => this.selectedReports.slice(),
reports => {
// ...
}
)
}
}
Call storeInstanceName.disposeReaction() whenever you're done with the reaction.
Notice that I've used .slice() here. This is because if you simply pass the array reference, the reaction will never be called. See reaction docs: you have to actually use the value in some way.
You also need to tweak the async code a bit. This:
async (reports) => {
await reports.forEach((report) => {
// ...
})
}
won't do what you hope, because forEach returns undefined. Even if you shift the async keyword to the forEach callback, all the API requests will be sent in quick succession. Consider using something like this instead, depending on whether you want to wait for the preceding request before sending the next one:
try {
for (const report of reports) {
await this.fetchReportParameters(report.id)
}
} catch (e) {
// handle error
}
This isn't always the right answer: sometimes it's fine to send a bunch of requests in quick succession (perhaps especially if it's a small batch, and/or in the context of HTTP/2). If that's ok with you, you could use:
reports => {
// ...
reports.forEach(report => this.fetchReportParameters(report.id))
}

How to mock values of constant in tested function

I have a problem with mocking constant in my test. I have a file with app configuration. There are just keys with some values nothing more.
appConfig.js
//imports
...
export const CASHING_AMOUNT_LIMIT = 50;
export const CASHING_DELETE_AMOUNT = 25;
...
and this is the reducer that I want to test:
reducer.js
import {
CASHING_AMOUNT_LIMIT,
CASHING_DELETE_AMOUNT,
} from '../appConfig';
...
export const reducer = handleActions({
[REQUEST_DATA]: (state, action) => {
if (payload.count >= CASHING_AMOUNT_LIMIT) {
// do something with data if the condition is true
}
return {
...someState,
};
},
...
In my test, I want to change a value for CASHING_AMOUNT_LIMIT and check if reducer returns current store. And I don't know how to mock this variable in reducer.js. Here is my test:
...//imports
const mockValues = {
CASHING_AMOUNT_LIMIT: 10,
CASHING_DELETE_AMOUNT: 5,
};
jest.mock('../../appConfig.js', () => mockValues);
const {
CASHING_AMOUNT_LIMIT,
CASHING_DELETE_AMOUNT,
} = require('../../appConfig.js');
...
it('MY awesome test ', () => {
expect(CASHING_AMOUNT_LIMIT).toBe(10);
expect(CASHING_DELETE_AMOUNT).toBe(5);
// HERE is all ok CASHING_AMOUNT_LIMIT = 10 and the second variable is 5
// tests are OK
// ....
expect(storeWithCache.dispatch(requestFunction({ test: 'XX'})))
.toEqual(myStore);
...
In the end I use dispatch which call my reducer action and function in reducer.js it runs OK... but with old value for CASHING_AMOUNT_LIMIT it is still 50 (as in appConfig.js) and I need to set 10
Can somebody help me with mocking CASHING_AMOUNT_LIMIT in reducer.js?
This part
jest.mock('../../appConfig.js', () => mockValues);
Needs to be outside the describe block, under the imports.
I have a solution we have all mock in one file testenv.js and it is importing globally I put jest.mock('../../appConfig.js') there and it works!