testcafe requestLogger only logs the first test in the fixture - javascript

So I'm getting better with testcafe and one feature I'd like to learn is its RequestLogger.
So I've created an instance of it
import { RequestLogger } from 'testcafe';
const logger = RequestLogger(/some reg exp/, {
logRequestHeaders: true,
logRequestBody: true
});
export default logger;
and then tried to use it on a sample test fixture:
fixture `REQUEST LOGGER TEST`
.requestHooks(logger);
test('should contain 204 as a status code 1', async t => {
await t.useRole(role);
await model.doSmth(t);
await model.doSmthElse(t);
console.log(logger.requests);
await t
.expect(logger.requests[0].response.statusCode)
.eql(204);
await t
.expect(logger.requests[1].response.statusCode)
.eql(200);
});
While the first test works just fine, the second one, even if it's the same, will output an empty array once I'll try to console.log(logger.requests)
Any idea how to go about it?

I had the same problem because you have to wait for the Smart Assertion Query Mechanism of Testcafe before doing assertions on Logger.request array.
The documentation tells us that using count or contains predicates make Testcafe use the Smart Assertion Query mechanism
Suggestion from Marion may do the job if your request returns a 200 statusCode :
await t.expect(logger.contains(record => record.response.statusCode === 200)).ok();
I found simpler to do this instead, which does not depend on the returned http status
await t.expect(logger.count(() => true)).eql(1);
replace eql(1) with the number of requests you expect to be logged before doing your assertions

Related

React testing library - await findBy* vs promise.then

I have a test where I await for some text to be shown:
const allTodosPromise = findByText('All Todos')
My test actually checks if the HTMLElement returned has some style (to see if it is active or not):
expect(await allTodosPromise).toHaveStyle(activeItemStyle);
which fails with message "Unable to find an element with the text 'All Todos'...".
But, if I resolve my promise with a then the test passes, like:
allTodosPromise.then(htmlElement =>
expect(htmlElement).toHaveStyle(activeItemStyle)
);
Why? And yes, I did say that my test function is async.
Also trying to await in the first reference fails:
const allTodosElement = await findByText('All Todos');
You can try the waitFor function: https://testing-library.com/docs/dom-testing-library/api-async/#waitfor

Jest spyOn call count after mock implementation throwing an error

I have a program that makes three post requests in this order
http://myservice/login
http://myservice/upload
http://myservice/logout
The code looks something like this
async main() {
try {
await this.login()
await this.upload()
} catch (e) {
throw e
} finally {
await this.logout()
}
}
where each method throws its own error on failure.
I'm using Jest to spy on the underlying request library (superagent). For one particular test I want to test that the logout post request is being made if the upload function throws an error.
I'm mocking the post request by throwing an exception.
const superagentStub = {
post: () => superagentStub
}
const postSpy = jest.spyOn(superagent, 'post')
.mockImplementationOnce(() => superagentStub)
.mockImplementationOnce(() => { throw new Error() })
.mockImplementationOnce(() => superagentStub)
const instance = new ExampleProgram();
expect(async () => await instance.main()).rejects.toThrow(); // This is fine
expect(postSpy).toHaveBeenNthCalledWith(3, 'http://myservice/logout')
If I don't mock the third implementation, the test will fail as logout() will throw its own error since the third post request will fail as a live call.
The spy in this case reports that only 1 call is made to the post method of the underlying library.
http://myservice/login
I find this strange because I am expecting 3 calls to the spy
http://myservice/login
http://myservice/upload -> but it throws an error
http://myservice/logout
Please keep in mind how to use expect(...).rejects.toThrow(). It's a bit tricky, though: https://jestjs.io/docs/expect#rejects
BTW: It's always nice to have ESLint active when coding with JavaScript. The following rule might then warn you about your error: https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/valid-expect.md ("Async assertions must be awaited or returned.")
Solution
You are missing an await in the beginning of the second-last line of your test-code. Replacing that line with the following should hopefully solve your problem:
await expect(() => instance.main()).rejects.toThrow();
which is the same as
await expect(async () => await instance.main()).rejects.toThrow();
You state // This is fine in your variant, but actually it isn't. You have a "false positive", there. Jest will most likely also accept the second-last line of your test even if you negate it, i.e. if you replace .rejects.toThrow() with .rejects.not.toThrow().
If you have more than one test in the same test-suite, Jest might instead state that some later test fails - even if it's actually the first test which causes problems.
Details
Without the new await in the beginning of the given line, the following happens:
expect(...).rejects.toThrow() initiates instance.main() - but doesn't wait for the created Promise to resolve or reject.
The beginning of instance.main() is run synchronously up to the first await, i.e. this.login() is called.
Mostly because your mockup to superagent.post() is synchronous, this.login() will return immediately. BTW: It might be a good idea to always replace async functions with an async mockup, e.g. using .mockResolvedValueOnce().
The Promise is still pending; JavaScript now runs the last line of your test-code and Jest states that your mockup was only used once (up to now).
The test is aborted because of that error.
The call to instance.main() will most likely continue afterwards, leading to the expected error inside instance.main(), a rejected Promise and three usages of your mockup - but all this after the test already failed.

TestCafe: awaiting ClientFunction to resolve in expect method leads to unexpected behavior

I'm currently facing an issue where awaiting a TestCafe ClientFunction within a assertion query chain leads to the behavior demonstrated by the code below:
const getPageUrl = ClientFunction(() => window.location.href.toString());
// This test works fine
test("Test that works as expected", async t => {
await t
.click(pageElements.someNaviButton) // navigates me to the site with url extension /some/url
.expect(getPageUrl()).contains("/some/url")
});
// This test works fine as well
test("Another test that works as expected", async t => {
await t.click(pageElements.someNaviButton) // navigates me to the site with url extension /some/url
const myPageUrl = await getWindowLocation();
await t.expect(myPageUrl).contains("/some/url")
});
// This test fails
test("Test that does not work as expected", async t => {
await t
.click(pageElements.someNaviButton)
.expect(await getPageUrl()).contains("/some/url")
});
According to TestCafe's documentation, one has to await asynchronous ClientFunctions. This leads me to what irritates me: I expect the second test to pass, which it does. Awaiting the method that encapsulates the ClientFunction within the expect method of the third test seems to be problematic, but why is that?
Additional info that could be of interest: I'm using TestCafe version 1.14.0.
Please consider that a test actions chain is a single expression. So, in the third test, getPageUrl is calculated before the chain execution. At that moment, the test is still on the start page.
The first test passes because getPageUrl is calculated lazily via TestCafe Smart Assertion Query Mechanism. This is the most proper way to write this kind of tests.
The second test passes because you broke the test actions chain.

Testcafe introduce entry point for tests after authorization

I have a test suite(fixture) with multiple functional tests for test cafe. They look something like this:
fixture('Test suite')
.page(login_url)
.beforeEach(async (t) => {
await t.maximizeWindow()
await page.signIn('username', 'pass');
});
test('Test case 1', async (t) => {
await t.expect(page.title.textContent).eql('My Page');}
test('Test case 2', async (t) => {
await do some other stuff}
This obviously wastes a lot of time before each test because we need to log in each time.
How do I log in once, set that page as an entry point, and start each test from that page?
You can use the TestCafe Roles mechanism to log in only once. All further tests will not require additional authentication. Take a look at the following article for details: https://devexpress.github.io/testcafe/documentation/guides/advanced-guides/authentication.html#user-roles

How to really call fetch in Jest test

Is there a way to call fetch in a Jest test? I just want to call the live API to make sure it is still working. If there are 500 errors or the data is not what I expect than the test should report that.
I noticed that using request from the http module doesn't work. Calling fetch, like I normally do in the code that is not for testing, will give an error: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout. The API returns in less than a second when I call it in the browser. I use approximately the following to conduct the test but I also have simply returned the fetch function from within the test without using done with a similar lack of success:
import { JestEnvironment } from "#jest/environment";
import 'isomorphic-fetch';
import { request, } from "http";
jest.mock('../MY-API');
describe('tests of score structuring and display', () => {
test('call API - happy path', (done) => {
fetch(API).then(
res => res.json()
).then(res => {
expect(Array.isArray(response)).toBe(true);
console.log(`success: ${success}`);
done();
}).catch(reason => {
console.log(`reason: ${reason}`);
expect(reason).not.toBeTruthy();
done();
});
});
});
Oddly, there is an error message I can see as a console message after the timeout is reached: reason: ReferenceError: XMLHttpRequest is not defined
How can I make an actual, not a mocked, call to a live API in a Jest test? Is that simply prohibited? I don't see why this would fail given the documentation so I suspect there is something that is implicitly imported in React-Native that must be explicitly imported in a Jest test to make the fetch or request function work.
Putting aside any discussion about whether making actual network calls in unit tests is best practice...
There's no reason why you couldn't do it.
Here is a simple working example that pulls data from JSONPlaceholder:
import 'isomorphic-fetch';
test('real fetch call', async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users/1');
const result = await res.json();
expect(result.name).toBe('Leanne Graham'); // Success!
});
With all the work Jest does behind the scenes (defines globals like describe, beforeAll, test, etc., routes code files to transpilers, handles module caching and mocking, etc.) ultimately the actual tests are just JavaScript code and Jest just runs whatever JavaScript code it finds, so there really aren't any limitations on what you can run within your unit tests.

Categories