Testing with Karma Angular NgRX effects with applied timer - angularjs

Having the following NgRX effect
loadData$: Observable<any> = createEffect((): Observable<Action> => {
return this.actions$.pipe(
ofType(loadDataStarted),
switchMap(() => {
return timer(0, 10000).pipe(
switchMap(() => this.dataService.loadNewData().pipe(
)),
);
}),
);
});
after all the requirements are mocked try to test if dataService.loadNewData() is getting called, but the test will fail
beforeEach(() => {
service.loadNewData.and.returnValue(of(data));
});
it('should call service', fakeAsync(() => {
tick(10000)
expect(service.loadNewData).toHaveBeenCalledOnceWith();
}));
How do I mock correctly this case

It seems you perform the subscription to the effect outside of the actual test (the it block). This means the subscription lives outside of the fakeAsync timer and because of this, the tick(..) has no effect.
To solve this, make sure to setup everything inside your fakeAsync test block.
A general recommendation would be to use marble testing for this: https://ngrx.io/guide/effects/testing#marble-diagrams
Edit: A concrete solution is found in https://stackoverflow.com/a/65857864/2544163

Related

Jest mock an import from a utility file

I am trying to standardise the way that regularly mocked functions get mocked.
So I have a function which handles a call to a home screen context in useHomeScreenContext.ts.
export const useHomeScreenContext = () => useContext(/* context here */)
Then the component I am testing uses that hook
Component.tsx
export const Component = () => {
const context = useHomeScreenContext();
}
Then my test file for Component looks like
Component.test.tsx
const contextSpy = jest.spyOn(context, 'useHomeScreenContext');
it("works", () => {
contextSpy.mockReturnValue(fakeValue)
expect(fakeValue).toBeTruthy()
})
The issue that I have with this approach is that there will be a large number of files that will need to be tested by mocking this context and the actual mock is a little bit more complex than I have put in here. So what I would like to do is to standardise the mock using a utility function, so I have created a separate file called mockHomeScreenContext.ts which looks a little something like this.
const contextSpy = jest.spyOn(context, 'useHomeScreenContext');
export const mockHomeScreenContext = (context) => {
beforeAll(() => {
contextSpy.mockReturnValue(mergeDeepLeft(context, homeScreenContextDefaults));
});
afterAll(() => {
contextSpy.mockRestore();
});
};
With the idea being that it is used inside of a describe block and it will tidy itself up at the end of the describe block like so
describe('and does not have funds', () => {
mockHomeScreenContext(contextOverrides);
it("works", () => {
// Tests here
})
});
And this seems to work really well, until I have 2 describe blocks and then things start to go a bit haywire and some tests seem to mock the data correctly but then all following tests will fail as the mock function is not returning anything. Getting rid of the afterAll call to clean up the mock helps but it then infects other tests.
I have been round and round in circles for days trying to get this working and I feel like it really shouldnt be this difficult and I am just missing a little bit of an understanding as to how jest mocks work.
I think the problem is in the fact that your contextSpy is global for all tests, because it's created outside of mockHomeScreenContext function. As you can find in documentation, mockRestore would restore the original (non-mocked) implementation. You might try to use mockClear instead, as it's a softer version (I usually just use that).
However, I would propose to not mock your context like this, but rather create a mock provider that would cause context to work properly, without a need to do extensive mocking (which as you probably see can be quie painful).
Here's example of what I have in mind:
Usage in test:
<MockProvider context={contextOverrides}>
<YourComponent/>
</MockProvider>
And implementation:
const MockProvider = ({context, children}) => {
return (
<YourContext.Provider value={mergeDeepLeft(context, homeScreenContextDefaults)}>
{children}
</YourContext.Provider>
)
}
This way you would get rid of all those hard mocks and would use real context (with some mocked value).

Cypress e2e test without using wait function

I'm new in e2e testing with Cypress and I have a question.
I wrote a test and I want not to use Cypress wait() command in it.
it('Should modify checkbox state', () => {
login();
cy.visit(TabsSettings.SIZE_FORMATS).then(() => {
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]', { timeout: 10000 }).click().then(() => {
expect(
cy.xpath('(//input[#type="checkbox"])[1]')
.then(checkbox => checkbox).should('be.checked')
);
});
cy.get('span').contains('Change').click().then(() => {
cy.get('li').contains('Disable').click().then({ timeout: 5000 }, () => {
cy.wait(500);
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]').click().then(() => {
expect(
cy.xpath('(//input[#type="checkbox"])[1]')
.then(checkbox => checkbox[0]).should('be.checked')
);
});
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[18]').click().then(() => {
expect(
cy.xpath('(//input[#type="checkbox"])[2]')
.then(checkbox => checkbox[3]).should('not.checked')
);
});
});
cy.xpath('//span[contains(text(), "Disable Selected")]').click().then(() => {
cy.get('li').contains('Enable').click().then(() => {
expect(
cy.get('input[type=checkbox]')
.then(checkbox => checkbox).should('not.checked')
);
});
});
cy.get('input[type=checkbox]').then(el => el[0].click()).then(() => {
expect(
cy.get('input[type=checkbox]')
.then(checkbox => checkbox).should('be.checked')
);
}).then(() => {
cy.xpath('//i').then(x => x[1].click());
cy.get('input[type=checkbox]').should('not.checked');
});
cy.get('div[data-main-select=true]').then(list => list[1].click()).then(() => {
cy.xpath('(//li[contains(text(), "50")])[1]').click().then(() => {
cy.get('input[type=checkbox]').should(checkboxes => {
expect(checkboxes).to.have.length(51);
});
});
});
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]').click().then(() => {
cy.wait(150);
expect(
cy.get('input[type=checkbox]')
.then(checkbox => checkbox).should('be.checked')
);
});
});
});
});
My problem is that if I not use that cy.wait(500) command, the test fails.
I googled a lot but I cannot find an answer for my problem.
The project is an React project, using Hooks. What may be caused the problem is that my component renders multiple times after that click() event. The page does not reload, but the main component rerenders several times. If this is the problem, how can I wait for finishing all renderings, and just after that continue the test, without using the wait function ?
You shouldn't need to use wait with time, but sometimes you may need to wait for a certain request to finish. I find it's the case with some AJAX requests. You can get it to wait for a specific request to finish using route definitions:
cy.server()
cy.route('activities/*', 'fixture:activities').as('getActivities')
cy.route('messages/*', 'fixture:messages').as('getMessages')
// visit the dashboard, which should make requests that match
// the two routes above
cy.visit('http://localhost:8888/dashboard')
// pass an array of Route Aliases that forces Cypress to wait
// until it sees a response for each request that matches
// each of these aliases
cy.wait(['#getActivities', '#getMessages'])
// these commands will not run until the wait command resolves above
cy.get('h1').should('contain', 'Dashboard')
Read more here: https://docs.cypress.io/guides/guides/network-requests.html#Waiting
Looking at it a bit further, you seem to be using a promise-style syntax unnecessarily.
Because Cypress has automatic retry on (most) commands, you can just call the commands sequentially without awaiting the result with .then(... constructs.
As an example, your code might be revised to something like this,
it('Should modify checkbox state', () => {
login();
cy.visit(TabsSettings.SIZE_FORMATS);
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]', {timeout: 10000 }).click();
// cy.xpath('(//input[#type="checkbox"])[1]').then(checkbox => checkbox).should('be.checked')
cy.get('input[#type="checkbox"]).eq(0).should('be.checked'); // NB eq() has zero-based indexing.
cy.get('span').contains('Change').click();
cy.get('li').contains('Disable').click();
// OR if you have to wait for the Disable button/link to appear, use this
cy.contains('li', 'Disable').click();
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]').click();
cy.get('input[#type="checkbox"]).eq(0).should('be.checked');
...
I'm not familiar with xpath syntax, so the translation is a guess, but it seems Cypress' commands are a little more readable, have a flatter layout, PLUS you get auto-retry built in, which removes the need to wait for specific periods.
If you want to add to your question a pseudocode translation of the xpath expressions, I'll give you the equivalent Cypress commands.

debounceTime not limiting api calls

I have this code:
await commuteReportService.getAddresses(query).pipe(debounceTime(1000))
.subscribe((response: AddressesAPIResponse) => {
console.log('execute call', response);
});
However, in the network tab I still see that all the requests are being sent. This code is used in a autocomplete component where I want to limit the calls being sent.
I'm using rxjs 5.5, React and Typescript.
debounceTime is only applied to whatever comes after it, meaning it only applies to your 'subscribe' — not getAddresses(query), which is where I assume you're making calls.
Consider this minimal example:
// html
<input type="text" id="example" />
// js
const input = document.getElementById('example');
Rx.Observable
.fromEvent(input, 'keyup')
.map(v => {
console.log('called')
return v.target.value;
})
.debounceTime(500)
.subscribe(val => {
console.log(`Debounced: ${val}`);
});
Even though Debounced... is delayed, you'll still see called being logged to the console on every keystroke. If we change the order
// js
Rx.Observable
.fromEvent(input, 'keyup')
.debounceTime(500)
.map(v => {
console.log('called')
return v.target.value;
})
.subscribe(val => {
console.log(`Debounced: ${val}`);
});
Now both Debounced... and called are delayed (Here's the fiddle for the code above).

Exporting an array within an ".then" doesnt work

I'm new to NodeJS and are only familiar with Java. I'm trying to create a file that creates objects based on a database and adds them to an array. This array I want to be able to export so that I can use it throughout the whole program, but when I try to export the array it doesn't work. I've tried googling and understanding but haven't come across anything that was helpful unfortunately.
I hope that someone can help me understand
I've tried calling module.exports after the ".then" call, but it just returns an empty array because its async.
I've also tried calling module.exports = teams inside the .then call but it didn't work neither.
var teams = [];
function assignTeamsToClasses() {
return new Promise((resolve, reject) => {
getAllTeamsInDb((teamList) => {
teamList.forEach((aTeam) => {
let newTeam = new Team(aTeam['teamid'], aTeam['teamname'], aTeam['teamrank']);
teams.push(newTeam);
});
resolve();
});
})
}
assignTeamsToClasses().then(() => {
module.exports = teams;
});
main.js
var teams = require('./initialize.js');
console.log(teams);
I expect it to return all teams that are in the database. I know that array is not empty when called within the ".then" call, but the export part does not.
Simple
the sequence require() + console.log() is synchronous
assignTeamsToClasses() is asynchronous, i.e. it updates teams at some unknown later point in time.
You'll have to design your module API to be asynchronous, e.g. by providing event listener interface or Promise interface that clients can subscribe to, to receive the "database update complete" event.
A proposal:
module.exports = {
completed: new Promise(resolve =>
getAllTeamsInDb(teams => {
const result = [];
teams.each(aTeam =>
result.append(new Team(aTeam.teamid,
aTeam.teamname,
aTeam.teamrank)
)
);
resolve(result);
})
),
};
How to use it:
const dbAPI = require('./initialize.js');
dbAPI
.completed
.then(teams => console.log(teams))
.catch(error => /* handle DB error here? */);
Every caller who uses this API will
either be blocked until the database access has been completed, or
receive result from the already resolved promise and proceed with its then() callback.

How do I unit test calling a service with promise-retry using Mocha?

I have an action in my ReactJS project that calls a notification service. It is required that, if the service call fails once, I must try calling the service again only one time before proceeding with the error state in the application. I used the promise-retry module for this and was able to get it working locally. However, I am now trying to write unit tests (Mocha) for the promiseRetry-wrapped service calls themselves and having incredible difficulty getting meaningful tests to pass. First, here is the action that calls the service, wrapped in a promiseRetry.
import promiseRetry from 'promise-retry';
...
const sendNotification = () => {
return (dispatch, getState) => {
const request = buildNotificationRequest(getState);
dispatch(createNotificationAttempt());
promiseRetry((retry) => {
return createNotificationService(request)
.catch(retry);
}, {retries: 1}).then(
() => {
dispatch(createNotificationSuccess());
},
(error) => {
dispatch(createNotificationError(error));
}
);
};
};
Typically, the way that I would write unit tests for actions calling services is something like this:
describe('notification actions', () => {
beforeEach(() => {
sendNotification = sinon.stub(services, 'createNotificationService').returns(Promise.resolve({}));
});
it('should log an attempt', () => {
store.dispatch(notificationActions.sendNotification());
const actions = store.getActions();
expect(actions[0].type).to.equal(notificationActions.ACTION_TYPES.CREATE_NOTIFICATION_ATTEMPT);
});
});
This works fine for testing the initial attempt, but for some reason, even though I can debug and step through the tests and hit all of the code inside the promiseRetry, the actions inside of them (such as dispatch(createNotificationSuccess())) are not logged in the store, so I cannot run expect statements on them. Every angle I have tried up to this point only retrieves the attempt from the store, and I cannot get any data from the success or failure side of the Promise.
I have found some information on Stack Overflow about testing promise-retry itself, but I need to know that if I stub the service I'm calling and force it to fail, that it will log another attempt and another failure. Or, if I stub the service and force it to succeed, it will only log one attempt, one success, and complete. As I mentioned previously, the only action I am getting in the store is the attempt, and nothing about success or failure, even though stepping through debug shows that all of those lines of code are hit.
Here is an example of a test that I cannot get to pass:
import * as services from 'services.js';
...
describe('the first time the service call fails', () => {
const error = {status: 404};
beforeEach(() => {
sendNotification = sinon.stub(services, 'createNotificationService').returns(Promise.reject(error));
});
it('should log a retry', () => {
store.dispatch(notificationActions.sendNotification());
const actions = store.getActions();
expect(actions[0].type).to.equal(notificationActions.ACTION_TYPES.CREATE_NOTIFICATION_ATTEMPT); // this passes
expect(actions[1].type).to.equal(notificationActions.ACTION_TYPES.CREATE_NOTIFICATION_FAILURE); // this fails because there are no other actions logged in the store.
Maybe I am misunderstanding the way promise-retry works? Shouldn't it hit my error action (dispatch(createNotificationError(error)) the first time it fails, and the second time (if applicable)? If not, it should be at least logging two attempts. Any advice?

Resources