How to assert function invocation order in jest - testing

I am mocking two functions with with jest.fn:
let first = jest.fn();
let second = jest.fn();
How can I assert that first called before second?
What I am looking for is something like sinon's .calledBefore assertion.
Update
I used this simple "temporary" workaround
it( 'should run all provided function in order', () => {
// we are using this as simple solution
// and asked this question here https://stackoverflow.com/q/46066250/2637185
let excutionOrders = [];
let processingFn1 = jest.fn( () => excutionOrders.push( 1 ) );
let processingFn2 = jest.fn( () => excutionOrders.push( 2 ) );
let processingFn3 = jest.fn( () => excutionOrders.push( 3 ) );
let processingFn4 = jest.fn( () => excutionOrders.push( 4 ) );
let data = [ 1, 2, 3 ];
processor( data, [ processingFn1, processingFn2, processingFn3, processingFn4 ] );
expect( excutionOrders ).toEqual( [1, 2, 3, 4] );
} );

The solution by clemenspeters (where he wanted to make sure logout is called before login) works for me:
const logoutSpy = jest.spyOn(client, 'logout');
const loginSpy = jest.spyOn(client, 'login');
// Run actual function to test
await client.refreshToken();
const logoutOrder = logoutSpy.mock.invocationCallOrder[0];
const loginOrder = loginSpy.mock.invocationCallOrder[0];
expect(logoutOrder).toBeLessThan(loginOrder)

Instead of your workaround you can install jest-community's jest-extended package which provides support for this via .toHaveBeenCalledBefore(), e.g.:
it('calls mock1 before mock2', () => {
const mock1 = jest.fn();
const mock2 = jest.fn();
mock1();
mock2();
mock1();
expect(mock1).toHaveBeenCalledBefore(mock2);
});
Note: per their doc you need at least v23 of Jest to use this function
https://github.com/jest-community/jest-extended#tohavebeencalledbefore
P.S. - This feature was added a few months after you posted your question, so hopefully this answer still helps!

Related

React Native map "Undefined" is not a function

I'm trying to get data from API
but. I'm getting this error Error Image.
Here is my code.
const [datas, setDatas] = useState(" ");
const res = async () => {
const response = await axios.get("http://hasanadiguzel.com.tr/api/kurgetir");
setDatas(response.data.TCMB_AnlikKurBilgileri);
};
datas.map((item) => {
return (
<KurCard
title={item.Isim}
alis={item.BanknoteBuying}
satis={item.BanknoteSelling}
/>
);
});
How can I solve this?
I'm trying to map() datas, because I need it
Hi #n00b,
The data that datas is initially being set to an empty string, which does not have a map method. First, you need an empty array instead of an empty stringuseState([]). Now you can map.
const [datas, setDatas] = useState([]);
const res = async () => {
const response = await axios.get('http://hasanadiguzel.com.tr/api/kurgetir');
setDatas(response.data.TCMB_AnlikKurBilgileri);
};
{datas.length > 0 &&
datas.map((item) => {
return <KurCard title={item.Isim} alis={item.BanknoteBuying} satis={item.BanknoteSelling}/>
})
}
make sure you data. it has a length greater than 0 before trying to map over it.
Assuming your API request is valid, you would need to actually return something from the component itself and not just the array:
return datas.map((item) => {return <KurCard title={item.Isim} alis={item.BanknoteBuying} satis={item.BanknoteSelling}/>})

for loop only iterating twice with axios

const [Datalist,setDatalist] = useState([]);
useEffect(() => {
axios.get( 'http://0.0.0.0:8000/api/v1/questions/history/1')
.then(response => {
const questions = response.data;
const datalist = [];
for (let i = 0; i < questions.length - 1; i++) {
const data = new Object();
data.isExpanded = false;
data.question_id = questions[i].id;
data.question = questions[i].content;
data.type = questions[i].type;
data.commentType = questions[i].comment_type;
data.answer = [];
datalist.push(data);
}
setDatalist(datalist);
});
},[]);
I have three questions in my database currently. The for loop should be iterating through 0 to 2, however, it is only iterating twice.
And I'm also having problems putting the data into Datalist.
Anybody know where the issue is??
Thanks in advance!!
Change your for loop to this:
for (let i = 0; i < questions.length; i++)
Since you are iterating over each question you receive, you could use the map-method (if your environment supports ES6-Syntax - but since you're using react, it most likely dooes).
From the MDN Docs:
The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.
With map, your code could look like this:
(Also note the removal of const data = new Object();. you can initialize an object and assign its properties/values at the same time)
const [Datalist,setDatalist] = useState([]);
useEffect(() => {
axios.get( 'http://0.0.0.0:8000/api/v1/questions/history/1')
.then(response => {
const questions = response.data;
const datalist = questions.map(question => {
return {
isExpanded: false;
question_id: question.id;
question: question.content;
type: question.type;
commentType: question.comment_type;
answer: [];
};
});
setDatalist(datalist);
});
},[]);

TestCafe - Can You Pass ctx (Context) Variables to reporter?

I would like to know if I have a context variable like t.ctx.data, is there a way to get that to write the value of t.ctx.data to the TestCafe JSON reporter (or any reporter)?
My code:
// Called within Express.js by a request coming from req
const testMySite = (req, res) => {
process.env.PARAMS = JSON.stringify(req.body)
let testcafe = null;
console.log(`Running test on ports 1341 and 1342`)
createTestCafe('localhost', 1341, 1342, void 0, true)
.then(tc => {
testcafe = tc;
const runner = testcafe.createRunner()
return runner
.src(`${path.dirname(__filename)}/tests/gisTest.js`)
.browsers('firefox:headless')
.reporter('json', 'report.json')
.run()
})
.then(failedCount => {
testcafe.close()
})
res.json({message: `Success! Scraper has begun to process ${req.body}`});
}
My test code:
import { ClientFunction, Selector } from 'testcafe';
const doc = process.env.PARAMS
const newDoc = JSON.parse(process.env.PARAMS)
console.log(`newDoc (from test)`, newDoc)
// const _id = newDoc._id
let data = newDoc.mydata
fixture `My Fixture`
.page('https://www.mysite.co')
.afterEach(async t => {
await t
// how do I get t.ctx.myData into the reporter??
console.log(`t.ctx.myData: `, t.ctx.myData)
})
test(`My Test`, async t => {
const photoIcon = Selector('div#sbtc div.LM8x9c > span')
const photoFieldForPaste = Selector('input#Ycyxxc')
const searchByImageButton = Selector('td#aoghAf > input')
const targetElement = Selector('div#jHnbRc span:nth-child(2) > a')
await t
.wait(1000)
.click(photoIcon)
.typeText(photoFieldForPaste, data, {paste: true})
.click(searchByImageButton)
if(await targetElement.exists && await targetElement.visible) {
await t.ctx.finalData = targetElement.innerText;
}
await t.ctx.finalData = null;
})
Please see the part // how do I get t.ctx.myData into the reporter??.
I am assuming this is the only place where I could potentially get the data from the test into the reporter but I'm not sure exactly how.
If you know how to get the t.ctx.myData variable as shown in the above code to be written to the JSON reporter, I would highly appreciate it.
Even better would be to have a way to send the t.ctx.myData value into the response.
At present, you can add only static metadata to tests and fixtures. This metadata is available in reports. Please refer to the following article to get details: https://devexpress.github.io/testcafe/documentation/guides/basic-guides/organize-tests.html#specify-test-metadata
As for sending dynamic data to the reporter, we keep this feature in mind, however we cannot give any estimates on this. Please track the following issue: https://github.com/DevExpress/testcafe/issues/3584

RxJs Marble testing concatMap with withLatestFrom

How can be unit tested this Observable?
e1.pipe(
concatMap(x => of(x).pipe(withLatestFrom(e2)))
);
Following unit test fails:
it('test', () => {
const e1 = hot( '--1^---2----3-|');
const e2 = hot( '-a-^-b-----c--|');
const expected = cold( '----x----y-|', {
x: ['2', 'b'],
y: ['3', 'c']
});
const result = e1.pipe(
concatMap(x => of(x).pipe(
withLatestFrom(e2))
)
);
// but this works:
// const result = e1.pipe(withLatestFrom(e2));
expect(result).toBeObservable(expected);
});
How the marbles should be written in order to pass this unit test? What did I do wrong?
I expect by inserting concatMap operator in the chain (before withLatestFrom) I have to also somehow "mark" it in the marbles.
In your real example
e1.pipe(
concatMap(x => of(x).pipe(withLatestFrom(e2)))
);
everything works fine probably because is either a BehaviorSubject or a ReplaySubject, which it's not case in your test.
Although you're using hot( '-a-^-b-----c--|');, it does not imply that you're using a BehaviorSubject. If we look at the implementation, we'll see that HotObservable extends the Subject class:
export class HotObservable<T> extends Subject<T> implements SubscriptionLoggable { /* ... */ }
which should help understand why this works:
const result = e1.pipe(withLatestFrom(e2));
and this doesn't:
const result = e1.pipe(
concatMap(x => of(x).pipe(
withLatestFrom(e2))
)
);
In the first snippet, e2 is subscribed when e1 is subscribed. In the second one, because you're using concatMap, every time e1 emits, withLatestFrom(e2)) will be subscribed and then unsubscribed, due to the complete notification that comes from of(x).
With this in mind, here would be my approach:
Note: I'm using the built-in functions provided by rxjs/testing
it('test', () => {
// might want to add this in a `beforeEach` function
let testScheduler = new TestScheduler(
(actual, expected) => (console.log({actual, expected}),expect(actual).toEqual(expected))
);
testScheduler.run(({ hot, expectObservable }) => {
const e1 = hot( '--1^---2----3-|');
const e2src = hot( '-a-^-b-----c--|');
const e2 = new BehaviorSubject(undefined);
const result = e1.pipe(
concatMap(x => of(x).pipe(
withLatestFrom(e2))
)
);
const source = merge(
result,
e2src.pipe(
tap(value => e2.next(value)),
// this is important as we're not interesting in `e2src`'s values
// it's just a way to `feed` the `e2` BehaviorSubject
ignoreElements()
)
);
expectObservable(source).toBe('----x----y-|', {
x: ['2', 'b'],
y: ['3', 'c']
});
});
})

Debounce mobx-react and props

I'm trying to debounce the method call: "chart.calculateChartData(props.answers)".
I tried:
- autorun
- reaction
- use-debounce from a react lib.
- setTimeout within calculateChartData
Each solution led to an update cycle or didn't work because MobX is not immutable.
Has someone a hint?
function QuantificationChart(props: QuantificationChartProps) {
const {t} = useTranslation();
const rootStore = useRootStore();
const chart = rootStore.formulaStore.getChart<Chart>(Chart.chartName);
const data = chart.calculateChartData(props.answers);
calculateChartData = (questionData: { [id: string]: number; } = {}) => {
let chartData = [];
for (let i = 0; i < this.numberOfYears + 1; ++i) {
let customData = {...questionData, "year" : i};
let chartEntry = {
cost: this.rootStore.formulaStore.calculateFormula(this.cost.formula, customData),
earn: this.rootStore.formulaStore.calculateFormula(this.earn.formula, customData),
sum: 0
};
chartEntry.sum = (chartEntry.earn - chartEntry.cost) + (chartData[i - 1]?.sum || 0);
chartData.push(chartEntry);
}
return chartData;
};
Hint: It's my first project with MobX
Found a solution. Seems to work:
Based on: https://mobx-react.js.org/recipes-effects
const [data, setData] = React.useState(chart.calculateChartData(props.answers));
React.useEffect(
() =>
autorun(() => {
setData(chart.calculateChartData(props.answers));
}, {delay: 1000}),
[],
);