Frame differs in running all test from running only one test, why? - rxjs

If I run all tests for one epic at once only the first test passes. The other tests fail because the frame differs. But every test singly run passes.
I could not find any related problem to this nether found something in the RxJS not the redux observable docs.
I thought there could be some kind of a reset function on the TestScheduler but there isn't.
One of my test (they all look pretty simular):
test('should fail if e-mail is missing', () => {
testScheduler.run(({ hot, expectObservable }) => {
const action$ = new ActionsObservable(
hot('-a', {
a: login('', 'secret')
})
);
const output$ = epic(action$, null, null);
expectObservable(output$).toBe('-a', {
a: failure(
formErrors.credentialsEmpty(['email', 'password'])
)
});
});
});
I expect the frame of output marble to be 1 but it is 2.
The output of a failing test:
Array [
Object {
- "frame": 1,
+ "frame": 2,
"notification": Notification {
"error": undefined,
"hasValue": true,
"kind": "N",
"value": Object {
edit
I could get around that behaviour by creating one TestScheduler instance per test but I am not sure if I am supposed to do it this way.

Stumbled across this today. I think creating one new TestScheduler per test is probably a good idea. It doesn't seem to have a noticeable impact between tests - and that way you're sure that the state is reset between tests.
One other workaround is to do testScheduler.frame = 0 in a beforeEach - but I opted to just create it from scratch each time.

Related

How to properly add a child generator (sub-flow) to an Observable on RxJS?

I'm using RxJS 7, and I would like to have a child generator (another Observable) emitting values based on the parent data.
I was already able to achieve this, but the solution I found is not efficient in terms of CPU usage because it needs to build a new RxJS pipeline for every parent item, and I believe I'm not using here the full potential RxJS has.
Constraints:
Emitted values from the Parent needs to be available to child generator;
Parent needs to know when child flow is done;
Child Observable can have many operators;
Efficient!
The working example:
const { from, mergeMap, reduce, lastValueFrom } = rxjs
function run() {
const parentData = [{ parentId: 1 }, { parentId: 2 }, { parentId: 3 }]
from(parentData)
.pipe(mergeMap((parent) => lastValueFrom(getChildFlow(parent))))
.subscribe((parent) => console.log(parent))
}
function getChildFlow(parent) {
return from(childGenerator(parent))
.pipe(reduce((acc, value) => {
acc.inner.push(value)
return acc
}, { inner: [] }))
}
async function* childGenerator(parentData) {
for await (const index of [1, 2, 3]) {
yield { childId: index, ...parentData }
}
}
run()
<script src="https://unpkg.com/rxjs#^7/dist/bundles/rxjs.umd.min.js"></script>
The reason I'm looking for a more efficient implementation is because it's intended for a data intensive system which can have millions of items flowing.
Questions!
Does RxJS provide some operator to cover this scenario in a more efficient implementation? I really dug RxJS's documentation and didn't found anything, but I may have missed it.
Would it be possible to reuse the flow on the above implementation? The tricky part here is that the child generator needs to have the parent data.
PS: Don't mind the implementation details of the code above, it's just an example of what I'm trying to achieve, and doesn't cover all the precautions and additional steps I have to justify the use-case.
I found the solution to my problem.
It required using mergeMap, groupBy, reduce and zip.
I'm not convinced it's the best solution, so if you find another approach for this that you think is more efficient, I will certainly upvote your answer and mark it as correct answer over mine.
const { from, mergeMap, tap, zip, map, groupBy, reduce } = rxjs
function run() {
const parent$ = from([{ parentId: 1 }, { parentId: 2 }, { parentId: 3 }])
.pipe(tap(doWhatever))
const reducer = reduce(accumulator, [])
const child$ = parent$
.pipe(mergeMap(childGenerator))
.pipe(tap(doWhatever))
.pipe(groupBy((p) => p.parentId))
.pipe(mergeMap((group$) => group$.pipe(reducer)))
zip([parent$, child$])
.pipe(map((results) => ({ ...results[0], inner: results[1] })))
.pipe(tap(doWhatever))
.subscribe(console.log)
}
function accumulator(acc, cur) {
return [...acc, cur]
}
function doWhatever() {}
async function* childGenerator(parentData) {
for await (const index of [1, 2, 3]) {
yield { childId: index, ...parentData }
}
}
run()
<script src="https://unpkg.com/rxjs#^7/dist/bundles/rxjs.umd.min.js"></script>

Cypress - Loop looking for data and refresh if not found

I need to loop looking for an item in a table and if it's not found, click a refresh button to reload the table. I know I can't use a simple while loop due to the asynchronous nature of cypress. Is there another way to accomplish something like this.
I tried to tweak an example from another post but no luck. Here's my failed attempt.
let arry = []
for (let i = 0; i < 60; i++) { arry.push(i) }
cy.wrap(arry).each(() => {
cy.get('table[class*="MyTableClass"]').then(function($lookForTheItemInTheTable) {
if($lookForTheItemInTheTable.find("MySearchValue")) {
return true
}
else {
cy.get('a[class*="selRefreshTable"]').click()
cy.wait(2000)
}
})
})
Cypress is bundled with lodash. Instead of using a for loop, you can the _.times(). However, I wouldn't recommend this for your situation as you do not know how many times you would like to reiterate.
You'll want to use the cypress-recurse plugin and use it like this example in that repo:
import { recurse } from 'cypress-recurse'
it('gets 7 after 50 iterations or 30 seconds', () => {
recurse(
() => cy.task('randomNumber'), // actions you want to iterate
(n) => n === 7, // until this condition is satisfied
{ // options to pass along
log: true,
limit: 50, // max number of iterations
timeout: 30000, // time limit in ms
delay: 300 // delay before next iteration, ms
},
)
})
Even with the above mentioned, there may be a simplified approach to solving your problem with setting up your app to have the table always display what you are seeking on the first try.

How to map different JSON objects from the fixture into specific spec test file in the cypress

I have the below Input.json as fixture and It contains two different test cases.
Input.json (Fixture folder)
[
{
"searchKeyword":"cypress"
},
{
"username":"QATesting",
"password":"testprofile"
}
]
The above data will validate two different functionality of Google. One is going to validate search engine and another one is going to validate the user login activity (This is just for sample use case which may imitate my actual requirement).
I just created the cypress runner and I just want to run the spec file by using the below runner.js file
const cypress = require('cypress')
const fixtures = require('./cypress/fixtures/Test.json')
const promises = fixtures.map(fixture => {
return cypress.run({
env: {
fixture
},
spec: './cypress/integration/test.spec.js',
});
});
I just added two different It(test cases) respectively in the below "test.spec.js" file. And one test is gonna do the search function and another one is gonna check the existing user login activity:
describe("How to map two different data set with respective test function",() =>{
const baseUrl = "https://www.google.com/";
const testData = Cypress.env('fixture')
beforeEach("",()=>{
cy.visit(baseUrl);
});
it("Test Case1: Search the keyword", function () {
cy.xpath("//input[#name='q']").type(testData.searchKeyword);
cy.xpath("//input[#value='Google Search']").click();
cy.get("//ul/li[2]").should("be.visible");
});
it("Test Case2: login to the gmail account", function(){
cy.xpath("//a[contains(text(),'Sign in')]").click();
cy.xpath("//div[contains(text(),'Use another account')]").click();
cy.xpath("#identifierId").type(testData.username);
cy.xpath("//*[contains(text(),'Next')]").click();
cy.xpath("#password").type(testData.password);
cy.xpath("#submitbtn").click();
})
});
But the second test is getting failed and the testData.username return undefined.
Is there anyway to map the specific JSON array object with specific function in the test.spec.js file?
Not sure how to map the first dataset index with first It (Test case 1) and second dataset index with second test case respectively.
One quick way is to skip if the testData does not have the required properties,
describe("How to map two different data set with respective test function",() =>{
const baseUrl = "https://www.google.com/";
const testData = Cypress.env('fixture')
beforeEach("",()=>{
cy.visit(baseUrl);
});
it("Test Case1: Search the keyword", function () {
if (!testData.searchKeyword) this.skip
cy.xpath("//input[#name='q']").type(testData.searchKeyword);
cy.xpath("//input[#value='Google Search']").click();
cy.get("//ul/li[2]").should("be.visible");
});
it("Test Case2: login to the gmail account", function() {
if (!testData.username) this.skip
cy.xpath("//a[contains(text(),'Sign in')]").click();
cy.xpath("//div[contains(text(),'Use another account')]").click();
cy.xpath("#identifierId").type(testData.username);
cy.xpath("//*[contains(text(),'Next')]").click();
cy.xpath("#password").type(testData.password);
cy.xpath("#submitbtn").click();
})
});
Tagging
You can also get into tags, adding a tag property to the testData
[
{
"tag": "search",
"searchKeyword":"cypress"
},
{
"tag": "user",
"username":"QATesting",
"password":"testprofile"
}
]
Perhaps use a library like cypress-tags, then in the runner script
const cypress = require('cypress')
const fixtures = require('./cypress/fixtures/Test.json')
const promises = fixtures.map(fixture => {
if (fixture.tag) {
process.env.CYPRESS_INCLUDE_TAGS = fixture.tag
}
return cypress.run({
env: {
fixture
},
spec: './cypress/integration/test.spec.js',
});
});
Since your fixtures data is in a array and the username and password fields are at index 1, so in order to access those you have to use:
testData[1].username
testData[1].password
In case if you don't want to use the index value, change the fixture structure to:
{
"searchKeyword": "cypress",
"username": "QATesting",
"password": "testprofile"
}
And in your test directly use:
testData.username
testData.password

Correct way to expect/receive state with Enzyme & MockProvider

I'm testing a component that is using a graphql useLazyQuery. MockProvider is provided by the Apollo recommended library #apollo/react-testing. I want to test that a certain message is being rendered base off the length of the data that is returned from the query. I have html elements structured like this:
<div className="message" data-id={props.data ? props.data.specials.length > 0 ? 'valid' : 'invalid' : ''}>
...children
</div>
I read through Apollo's docs about testing and wrote up a test like this:
mock = {
request: {
query: GET_PRODUCTS,
variables: { zip: "91001" }
},
result: {
data: {
specials: [
{
"_id": "5ecf28c459d3781a2e99738e",
},
{
"_id": "5ecf28c459d3781a2e99738f",
}
]
}
}
}
wrapper = mount(
<MockedProvider mocks={[mock]} addTypename={false}>
<Store.Provider value={[{ loading: false, zip: null }]}>
<GetZipCode />
</Store.Provider>
</MockedProvider>
)
await wait(0)
expect(wrapper.find(message).prop('data-id')).toEqual('valid')
But I've found that the tests do not update based on the mock that I put it. I have a test about this where I'm passing this value as the mock:
mock = {
request: {
query: GET_PRODUCTS,
variables: { zip: "32005" }
},
result: {
data: { specials: [] }
}
}
...after tests
expect(wrapper.find(message).prop('data-id')).toEqual('invalid')
And for both of these tests the expected value is "" which is the initial value for my data-id prop in my html element. If I were to set the initial value to "invalid" compared to "" then the expected value in my test would output "invalid".
It seems that no matter what I passed my mock provider it doesn't wait for it to be passed. I'm using the wait package that Apollo recommends as well.
If you wanna mock using jest, you use the following approach
jest.mock("apollo-client", () => ({
__esModule: true,
useQuery: (query: any) => {
//other mocks if needed
},
useLazyQuery: jest.fn().mockReturnValue([
jest.fn(),
{
data: {
yourProperties: "Add Here",
},
loading: false,
},
]),
}));
As you see, this approach even returns the mock function that is called in the code to be tested.
I faced in similar issues while testing with useLazyQuery. I would suggest writing a custom hook on top of useLazyQuery. This will have two benefits:
No need to wrap your test instance in Mock provider. You can mock the entire module (custom hook) using jest and use mockReturnValue method to simulate different behaviours (loading, data, error)
You can easily switch between actual query or some mock data in local file system while development.
Refer this blog for implementation details and demo app.

RxJS: How many events is too many for combineLatest?

I've got a toolbar where each tool can be disabled/hidden independently. The tools depend on other system events and emit individual events configuring their availability. The toolbar uses combineLatest to pull all the tools together and emit a toolbar config.
The combineLatest is listening to 40+ events.
Will this be a performance problem? Is there a practical limit to how many events combineLatest can consume?
Hard to say just like that.
I think that having a huge number of streams combined is not a problem on it's own.
What could be:
- having those streams emitting a value very very often
- having those streams triggering Angular change detection (might be worth running them outside ng zone if possible)
That said, I think that the performance problem here is hiding a conception problem eventually. It really feels like you might need a "single source of truth". Having a look into Redux and eventually Ngrx might be a great help for you.
Then from the unique store, you could easily retrieve the availability of your tools.
The tools depend on other system events and emit individual events
The Redux pattern is generally playing very well with that kind of challenges:
- Async
- State
It really sounds like it might be a perfect fit here.
If you don't know where to start, I'd advise you to first read the Redux documentation. It's one of the best I've ever read: https://redux.js.org
Once you understand how Redux works and whether it's a good fit for you or not, if the answer is yes then take a look into Ngrx. As you seem to be working with streams a lot, if you take the time to learn Redux first then Ngrx will definitely not be a problem: https://redux.js.org
If you decide to go this way, good luck into this amazing journey of reactive and functional programming :)
EDIT 11/07:
If you think that Redux is overkill then maybe you could build a minimal solution that acts a bit like it. The following is completely type safe and you can update multiple properties without firing the final stream as many times as you update properties. Once is enough:
import { BehaviorSubject } from 'rxjs';
import { tap } from 'rxjs/operators';
type YourDataType = {
prop1: string,
prop2: string,
prop3: string,
prop4: string,
// ...
prop40: string,
};
const properties$: BehaviorSubject<YourDataType> = new BehaviorSubject({
prop1: '',
prop2: '',
prop3: '',
prop4: '',
// ...
prop40: '',
});
const patchProperties = (updatedProperties: Partial<YourDataType>) =>
properties$.next({
...properties$.getValue(),
...updatedProperties
});
properties$
.pipe(
tap(x => console.log(JSON.stringify(x, null, 2)))
)
.subscribe()
patchProperties({
prop3: 'new prop 3'
});
patchProperties({
prop1: 'new prop 1',
prop2: 'new prop 2',
prop3: 'final prop 3',
prop40: 'new prop 40',
});
Produces the following output:
{
"prop1": "",
"prop2": "",
"prop3": "",
"prop4": "",
"prop40": ""
}
{
"prop1": "",
"prop2": "",
"prop3": "new prop 3",
"prop4": "",
"prop40": ""
}
{
"prop1": "new prop 1",
"prop2": "new prop 2",
"prop3": "final prop 3",
"prop4": "",
"prop40": "new prop 40"
}
Here's a Stackblitz demo:
https://stackblitz.com/edit/typescript-zafsnk?file=index.ts

Resources