react testing, button not clicking from userevent - react-testing-library

im trying to do a write a test where i can click a button in a react component but its not working and i don't know why. as far as i can tell this should be working. im expecting the callback to be called once.
it("should work", () => {
var mockCallback = jest.fn();
render(<button onClick={mockCallback}>hello world</button>);
act(() => {
let test = screen.getByRole("button");
console.log(test);
userEvent.click(test);
});
expect(mockCallback).toHaveBeenCalledTimes(1);
});
here is a link to a codesandbox
what am i doing wrong?

userEvent.click returns a Promise<void> as you can see here. So, to your test work you need to make your test async and await the click event:
it("should work", async () => {
var mockCallback = jest.fn();
render(<button onClick={mockCallback}>hello world</button>);
const button = screen.getByRole("button");
screen.debug();
await userEvent.click(button);
expect(mockCallback).toHaveBeenCalledTimes(1);
});
* Also, you donĀ“t need the act in this case. Check more about it here.

Related

Jest + Axios: Test Whether Promise Was Fulfilled

Using Vue test utils with Jest, I am attempting to spy on an axios.get call which runs in my component's created() lifecycle hook. I would like to test that the promise has been fulfilled (i.e. was not rejected) based on a method that runs in the then() block, but not the catch() block.
The way I have the test setup right now, the spyOn always resolves because I am using .mockResolveValue(). I tried declaring mockResolveValue() in the it('') portion of the test, but it says the axios get is called 0 times when I do that.
DeptPrefs.vue
<script>
import axios from 'axios'
export default {
name: "DeptPrefs",
data () {
return {
...
}
},
async created () {
await this.getPrefs()
},
computed: {},
methods: {
async getPrefs () {
await axios.get('/coolsite/api/admin/getstuff?dept=216001')
.then(r => {
this.setDeptPrefs(r.data)
}).catch((e) => {})
}
...
</script>
And this is how the deptprefs.spec.js currently looks.
describe('when prefs created() hook loads', () => {
const localVue = createLocalVue()
localVue.use(Vuex)
...
const getPrefs = jest.spyOn(DeptPrefs.methods, 'getPrefs') // spy on getPrefs() method
jest.spyOn(axios, 'get').mockResolvedValue([]) // spy on axios .get() method
const wrapper = mount(DeptPrefs, {
...
})
it('makes an api call to get the dept prefs', async () => {
await expect(getPrefs).toHaveBeenCalled() // works fine
await expect(axios.get).toHaveBeenCalledTimes(1) // works fine
const setDeptPrefs = jest.spyOn(appModule.mutations, 'setDeptPrefs')
expect(setDeptPrefs).toHaveBeenCalledTimes(1) // this is the expectation in question. It's always 1.
})
})
So let's say I change the URL of the API call to be a non-existent route. The test will still say that the setDeptPrefs() method has been called 1 time. It shouldn't be called at all because the axios should've hit the catch block, where that method does not run.
I want to amend my test to reflect this; when the then() block is hit, setDeptPrefs should NOT be called. When it does, it should be.
Also please feel free to chime in on if this is something I should even be unit testing. I am trying to get a feel for what's test-worthy and what isn't.

Forced to re-query element in order to fire click handler

I have a test that where I assert that my API request is supplying the correct params. I am using MSW to mock the api, and I supply a spy for the request handler:
test("supplies order direction descending if header button is clicked twice", async () => {
const mockHandler = jest.fn(handler);
server.use(rest.get(`${config.apiHost}/targets`, mockHandler));
// First request to /targets happens on render
render(<Route>{(props) => <TargetList history={props.history} />}</Route>);
const button = await screen.findByRole("button", { name: "Port" });
// Second request to /targets happens on button click
userEvent.click(button);
await waitFor(() => {
expect(mockHandler).toHaveBeenCalledTimes(2);
});
// Third request to /targets SHOULD happen here but doesn't
userEvent.click(button);
await waitFor(() => {
expect(mockHandler).toHaveBeenCalledTimes(3);
const lastCall = mockHandler.mock.calls[2];
const requestArg = lastCall[0];
expect(requestArg.url.searchParams.get("orderby")).toBe("port");
expect(requestArg.url.searchParams.get("direction")).toBe("descending");
});
});
The above code doesn't work, as firing the click event a second time on the button appears to do nothing. However, if I requery for the button, then I can successfully fire the handler:
test("supplies order direction descending if header button is clicked twice", async () => {
...
const button = await screen.findByRole("button", { name: "Port" });
// Second request to /targets happens on b utton click
userEvent.click(button);
await waitFor(() => {
expect(mockHandler).toHaveBeenCalledTimes(2);
});
// Third request to /targets now works!
const button2 = await screen.findByRole("button", { name: "Port" });
userEvent.click(button2);
await waitFor(() => {
expect(mockHandler).toHaveBeenCalledTimes(3); // SUCCESS!
...
});
});
Why do I have to re-query for the same element? Am I doing something incorrectly?
I do not have complete source code but I would assume you are doing something on your click event handler that would force react to render again and in that case old element would no longer be present on DOM and hence click event handler will not be invoked.
P.S: You can use react life cycle hooks to determine if component was re-rendered.

Protractor and locating element

I need to locate elemet "username" using Protractor.
I have used
browser.waitForAngularEnabled(false);
due to the fact that the login page is not Angular.
My script is as simple as this:
describe('slowchat', function() {
it('start test', function() {
browser.waitForAngularEnabled(false);
browser.get('https://www.test.no/eai/bankid20');
element(by.css('[value="Logg inn"]')).click();
browser.waitForAngularEnabled(true);
});
});
However I get error:
Failed: No element found using locator: By(css selector, [value="Logg inn"])
Please see attachment for the place I want to locate.
Any idea why this is not able to locate the button?
Now i saw that in circle is input with username but you ask about button to submit the form, btw you can turn off angular by flag
//entering non-angular site
browser.ignoreSynchronization=true;
//Some code
element(by.css('button[onclick="mySubmit()"])).click();
//when you go back to angular site
browser.ignoreSynchronization=false;
So when you disable waiting for angular or ignore synchronization, this means that you will need to roll your own waiting for some sort of stability. There are two ways you can accomplish this: 1. Turn off the control flow and use async await. 2. Resolve your promises before moving to the next action.
Resolving promises
I think you need to use the done() method call here because of the async calls. My general take away on resolving promises is ew... prefer using async / await and removing the control flow.
describe('slowchat', () => {
it('start test', (done) => {
browser.waitForAngularEnabled(false);
browser.get('https://www.test.no/eai/bankid20').then(() => {
return element(by.css('[value="Logg inn"]')).click().then() => {
browser.waitForAngularEnabled(true);
done();
});
});
});
});
Using async / await and removing the control flow
The control flow is being deprecated so this will get you ahead of the curve. I would suggest doing it this way. It will make your tests easier to debug.
describe('slowchat', () => {
it('start test', async() => {
await browser.waitForAngularEnabled(false);
await browser.get('https://www.test.no/eai/bankid20');
await element(by.css('[value="Logg inn"]')).click();
await browser.waitForAngularEnabled(true);
});
});
When launching this you will need to set the flag to turn off the control flow. In your config, you'll need to add this: SELENIUM_PROMISE_MANAGER: false

wait for http request to complete in protractor

I am trying to wait for spinner to disappear and then for my steps to execute but nothing is working for me.
browser.wait(function () {
return this.spinner.isDisplayed().then(function (result) {
return !result;});}, 20000);
and i even tried with
browser.wait(function () {
return !browser.isElementPresent(this.spinner);}, 20000);
even with below method
browser.sleep(1000);
this.spinner.isPresent().then(function (result) {
if (result === true) {
var EC = protractor.ExpectedConditions;
browser.wait(EC.invisibilityOf(this.spinner), 10000);}});
then only thing that works is
browse.sleep(10000);
i don't want to use sleep in my code. can anyone help me with how to wait for complete http request to complete and then process with testing
you should consider using Expected Conditions since they return true/false based on current conditions
http://www.protractortest.org/#/api?view=ProtractorExpectedConditions.prototype.invisibilityOf
so your test case would become:
browser.wait(EC.invisibilityOf(this.spinner),20000).then(function(){
...continue test, spinner gone
});
UPDATE
in order to use done, you would generally pass this cb into your it() function. This means your test could look like
describe("example describe",function(){
it("should be an example only", function(done){
request.get("www.google.com",function(res){
//done with async request, now call done
done();
})
})
});
Since your entire code isn't posted up here, you should have something similar to:
it("should wait for spinner to go bye-bye",function(done){
browser.wait(EC.invisibilityOf(this.spinner),20000).then(function(){
done()
});
});

Protractor - How to obtain the new URL

I am new to Protractor (and Javascript by the way), and I am writing some tests to practice. My goal so far is to check that when I click on the home button of a website, the redirection leads me correctly to the expected address.
I have written this:
var HomeTopBanner = function() {
this.homeUrl = browser.params.homePageObject.homeUrl;
this.topBanner = element(by.css('.navbar-inner'));
this.homeButton = this.topBanner.element(by.css('.icon-home'));
}
describe('Home button', function(){
var homeTopBanner = new HomeTopBanner();
var newUrl = '';
it('clicks on the Home button', function(){
homeTopBanner.homeButton.click();
browser.getCurrentUrl().then(function storeNewUrl(url) {
newUrl = url;
});
})
it('checks that the home button leads to the homepage', function(){
expect(newUrl).toEqual(homeTopBanner.homeUrl);
})
});
This works, but my question is:
Why do I need to separate the "GetCurrentUrl" and the "expect(newUrl)" parts? I would prefer to have both of them in the same spec, but if I do that, during the comparison of the expect, newUrl=''
I assume this is related to browser.getCurrentUrl() being a promise, but is there a better way to do it?
Yes, getCurrentUrl returns a promise with the url in the form of a string as explained in the protractor api docs. You have to wait until the url is returned in order to use it. Now in order to combine both the specs you can write your expect statement inside the function that getCurrentUrl returns as shown below and there is no need of using a newUrl variable too if you want -
it('clicks on the Home button', function(){
homeTopBanner.homeButton.click();
browser.getCurrentUrl().then(function(url) {
expect(url).toEqual(homeTopBanner.homeUrl);
});
})
There could also be another issue when after the click action the previous url is being captured due to the fact that protractor is async and fast. In that case you can write your getCurrentUrl() function inside the promise that click() function returns. Here's an example of it -
it('clicks on the Home button', function(){
homeTopBanner.homeButton.click().then(function(){
browser.getCurrentUrl().then(function(url) {
expect(url).toEqual(homeTopBanner.homeUrl);
});
});
})
Hope this helps.