Can you invoke a method in Cypress using dynamically instantiated objects? - nested

I'm trying to write e2e tests with Cypress and would like to dynamically be able to intantiate objects and use them as parameters for the invoke method.
The subject under test is a SPA with a redux store. For debugging (and possibly e2e testing), I have a window.debug object. The debug object exposes a dispatchAction(action: Action) method and properties that return classes, for instance Actions.SetMenu.
So on the browsers console I can call something like this to dispatch an action:debug.dispatchAction(new debug.Actions.SetMenu({ menu }))
For Cypress I need something like the following:
let setMenuAction;
cy.window()
.its('debug')
.then(debug => {
const SetMenu = debug.Actions.SetMenu;
setMenuAction = new SetMenu({ menu });
console.log(setMenuAction);
return debug;
})
.invoke('dispatchAction', setMenuAction);
But the above doesn't work. Is there a way to achieve what I'm trying?
Edit: Got it to work, was an async problem:
cy.window()
.its('debug.Actions.SetMenu')
.then(SetMenu => {
cy.window()
.its('debug')
.invoke('dispatchAction', new SetMenu({ menu }));
})

cy.window()
.its('debug.Actions.SetMenu')
.then(SetMenu => {
cy.window()
.its('debug')
.invoke('dispatchAction', new SetMenu({ menu }));
})

Related

How to mock a function and expect it to be called?

I am new to react-testing-library and I have been trying to test one function for a long time.
for example, I want to check if when a button is clicked a given function is called and it's throwing errors. so any help would be highly appreciated and if possible share with me any helpful resources.
signin.js
export default class SignIn extends Component {
constructor(props) {
super(props);
this.state = {
};
}
handleClose = (event, reason) => { };
validate = () => { };
change = (e) => { };
onSubmit = (e) => { };
render() {
return (<div>...</div>);
}
}
Full: https://github.com/blaise82/react-testing-library-try/blob/master/src/views/SignIn.js
this is my test
it('should submit form', async () => {
const { getByLabelText, getByText, container, debug } = render(<SignIn />);
const change = jest.fn();
const onSubmit = jest.fn();
const email = getByLabelText('email');
const password = getByLabelText('password');
const submit = getByLabelText('submit');
userEvent.type(email, 'octopusbn#gmail.com');
expect(email.value).toBe('octopusbn#gmail.com');
expect(password.value).toBe('');
expect(change).toHaveBeenCalled();
console.log(password)
await userEvent.click(submit);
expect(onSubmit).toHaveBeenCalled();
});
Full: https://github.com/blaise82/react-testing-library-try/blob/master/src/test/signin.test.js
results
> Expected number of calls: >= 1
> Received number of calls: 0
please let know what I am doing wrong.
Full code on GitHub: https://github.com/blaise82/react-testing-library-try
You can test a function by mocking all that is coming from outside of the component (aka dependencies) like - a prop callback, an external library api etc.
Before starting, let's go through what all functions are in the component.
Going through the component, I can list them as below:
Event handlers on elements [like handleClose, onSubmit, change in the component]
Functions internal to the component which do not interact with the state/functions outside the component [validate]
prop functions/library apis being called [axios.post]
Let's discuss them one by one --
Event handlers &
Functions internal to component not interacting with state/functions outside of the component
==> Event handlers that are attached to elements can safely be expected to get called. You don't need to test them if they are called. Rather, what you should test is the after-effect of them being called. Also the functions like validate
Let's take example of the change function that you are trying to test. This function after being called sets the state and the state gets reflected into the form elements. We can assert values of the form elements with a helper like this.
prop functions/library apis being called [axios.post]
==> These functions can be mocked and tested for the number of calls/parameters they are called with.
https://jestjs.io/docs/en/mock-functions.html#mocking-modules
In addition to the snippet of mocking jest as given in the link above, in your case -
axios.post.toHaveBeenCalledWith(expectedParms);
Also you can make it return results/errors you want and test respective component behaviour.
Hope you find this helpful. Cheers!
I think this is because you are not actually passing in your mocked functions to the component. You're just instantiating two constants that happen to have the name of the functions you're trying to watch, but are not actually being used anywhere in your component.
It sounds like you want to spy on your component's internal functions to see that they've been called.
Here's something of an example (not tested) based on a post (linked below) that might help you.
describe('spying on "onSubmit" method', () => {
it('should call onSubmit when the button is clicked', () => {
const wrapper = shallow(<SignIn />);
const instance = wrapper.instance();
jest.spyOn(instance, 'onSubmit');
wrapper.find('button').simulate('click');
expect(instance.onSubmit).toHaveBeenCalled();
});
});
Post: https://bambielli.com/til/2018-03-04-directly-test-react-component-methods/#spying-on-incrementcounter

Assert arguments of stubbed methods with sinon

import ManagerDaoStub from '../salesforce/__test__/ManagerDaoStub';
import criticalMerchants from '../criticalMerchants';
describe('criticalMerchants Unit Tests', () => {
before(() => {
ManagerDaoStub.initStubs();
});
after(() => {
ManagerDaoStub.restoreStubs();
});
it('assert the arguments of stubbed method', (done)=>{
let load = criticalMerchants.createCases(MERCHANT, DEVICE_ID, KEY, {});
return done();
});
})
This is the test file written in node criticalMerchants.test.js. The method i want to test which is createCases uses a method in ManagerDao, which has been stubbed in ManagerDaoStub as below.
import ManagerDao from '../ManagerDao';
class ManagerDaoStub {
constructor() {
this.sandbox = sinon.sandbox.create();
}
initStubs(sandbox) {
this.sandbox = sandbox || this.sandbox;
this.restoreStubs();
this.initFindOpenCases();
}
restoreStubs() {
this.sandbox.restore();
}
initFindOpenCases() {
let findOpenCases = this.sandbox.stub(ManagerDao, "findOpenCases");
findOpenCases
.withArgs(DEVICE_ID, KEY, match.func)
.callsArgWith(2, new Error("Test error"));
}
}
I want to assert whether this stubbed method initFindOpenCases was called with the right arguments (DEVICE_ID,KEY,null). I used
sinon.assert.calledWith(ManagerDaoStub.initFindOpenCases, DEVICE_ID, KEY, null) and this gives the following error:
AssertError: initFindOpenCases() is not stubbed.
Can someone suggest a proper way to do this?
First off, if ManagerDao.initFindOpenCases is an instance method (I'm unsure since you haven't shared its definition), then you can't stub it on the constructor like you've done here:
let findOpenCases = this.sandbox.stub(ManagerDao, "findOpenCases")
You need to either create an instance first-- then stub it on that instance-- or stub it on the prototype itself like so:
let findOpenCases = this.sandbox.stub(ManagerDao.prototype, "findOpenCases");
Secondly, you're making the same mistake again in your assertion, combined with another:
sinon.assert.calledWith(ManagerDaoStub.initFindOpenCases, DEVICE_ID, KEY, null)
ManagerDaoStub is the constructor, and it does not have an initFindOpenCases property. Its prototype does, and thus its instances do as well. On top of that, ManagerDaoStub.prototype.initFindOpenCases is still not a stub. It's a method you're calling to create a stub, but it is not itself a stub. More plainly, you're getting ManagerDao mixed up with ManagerDaoStub.
Assuming you make example change above, you can make your assertion work like this:
sinon.assert.calledWith(ManagerDao.prototype.initFindOpenCases, DEVICE_ID, KEY, null)
However, this isn't all I would recommend changing. This latter mixup is arising largely because you're vastly over-complicating the setup code of your test. You don't need to make an entire class to stub one method of ManagerDao.
Instead, just replace your before and after calls with these:
beforeEach(() => {
// Create the stub on the prototype.
sinon.stub(ManagerDao.prototype, 'findOpenCases')
.withArgs(DEVICE_ID, KEY, sinon.match.func)
.callsArgWith(2, newError('Test Error'));
});
afterEach(() => {
// As of sinon 5.0.0, the sinon object *is* a sandbox, so you can
// easily restore every fake you've made like so:
sinon.restore();
});
Aside from that, I recommend looking deeply into the difference between properties on a constructor and properties on its prototype. That knowledge will make stuff like this much easier for you. Best place to start is probably here on MDN.

Testing MutationObserver with Jest

I wrote a script with the main purpose of adding new elements to some table's cells.
The test is done with something like that:
document.body.innerHTML = `
<body>
<div id="${containerID}">
<table>
<tr id="meta-1"><td> </td></tr>
<tr id="meta-2"><td> </td></tr>
<tr id="meta-3"><td> </td></tr>
<tr id="no-meta-1"><td> </td></tr>
</table>
</div>
</body>
`;
const element = document.querySelector(`#${containerID}`);
const subject = new WPMLCFInfoHelper(containerID);
subject.addInfo();
expect(mockWPMLCFInfoInit).toHaveBeenCalledTimes(3);
mockWPMLCFInfoInit, when called, is what tells me that the element has been added to the cell.
Part of the code is using MutationObserver to call again mockWPMLCFInfoInit when a new row is added to a table:
new MutationObserver((mutations) => {
mutations.map((mutation) => {
mutation.addedNodes && Array.from(mutation.addedNodes).filter((node) => {
console.log('New row added');
return node.tagName.toLowerCase() === 'tr';
}).map((element) => WPMLCFInfoHelper.addInfo(element))
});
}).observe(metasTable, {
subtree: true,
childList: true
});
WPMLCFInfoHelper.addInfo is the real version of mockWPMLCFInfoInit (which is a mocked method, of course).
From the above test, if add something like that...
const table = element.querySelector(`table`);
var row = table.insertRow(0);
console.log('New row added'); never gets called.
To be sure, I've also tried adding the required cells in the new row.
Of course, a manual test is telling me that the code works.
Searching around, my understanding is that MutationObserver is not supported and there is no plan to support it.
Fair enough, but in this case, how can I test this part of my code? Except manually, that is :)
I know I'm late to the party here, but in my jest setup file, I simply added the following mock MutationObserver class.
global.MutationObserver = class {
constructor(callback) {}
disconnect() {}
observe(element, initObject) {}
};
This obviously won't allow you to test that the observer does what you want, but will allow the rest of your code's tests to run which is the path to a working solution.
I think a fair portion of the solution is just a mindset shift. Unit tests shouldn't determine whether MutationObserver is working properly. Assume that it is, and mock the pieces of it that your code leverages.
Simply extract your callback function so it can be tested independently; then, mock MutationObserver (as in samuraiseoul's answer) to prevent errors. Pass a mocked MutationRecord list to your callback and test that the outcome is expected.
That said, using Jest mock functions to mock MutationObserver and its observe() and disconnect() methods would at least allow you to check the number of MutationObserver instances that have been created and whether the methods have been called at expected times.
const mutationObserverMock = jest.fn(function MutationObserver(callback) {
this.observe = jest.fn();
this.disconnect = jest.fn();
// Optionally add a trigger() method to manually trigger a change
this.trigger = (mockedMutationsList) => {
callback(mockedMutationsList, this);
};
});
global.MutationObserver = mutationObserverMock;
it('your test case', () => {
// after new MutationObserver() is called in your code
expect(mutationObserverMock.mock.instances).toBe(1);
const [observerInstance] = mutationObserverMock.mock.instances;
expect(observerInstance.observe).toHaveBeenCalledTimes(1);
});
The problem is actually appears because of JSDom doesn't support MutationObserver, so you have to provide an appropriate polyfill.
Little tricky thought may not the best solution (let's use library intend for compatibility with IE9-10).
you can take opensource project like this one https://github.com/webmodules/mutation-observer which represents similar logic
import to your test file and make global
Step 1 (install this library to devDependencies)
npm install --save-dev mutation-observer
Step 2 (Import and make global)
import MutationObserver from 'mutation-observer'
global.MutationObserver = MutationObserver
test('your test case', () => {
...
})
You can use mutationobserver-shim.
Add this in setup.js
import "mutationobserver-shim"
and install
npm i -D mutationobserver-shim
Since it's not mentioned here: jsdom has supported MutationObserver for a while now.
Here's the PR implementing it https://github.com/jsdom/jsdom/pull/2398
This is a typescript rewrite of Matt's answer above.
// Test setup
const mutationObserverMock = jest
.fn<MutationObserver, [MutationCallback]>()
.mockImplementation(() => {
return {
observe: jest.fn(),
disconnect: jest.fn(),
takeRecords: jest.fn(),
};
});
global.MutationObserver = mutationObserverMock;
// Usage
new MutationObserver(() => {
console.log("lol");
}).observe(document, {});
// Test
const observerCb = mutationObserverMock.mock.calls[0][0];
observerCb([], mutationObserverMock.mock.instances[0]);
Addition for TypeScript users:
declare the module with adding a file called: mutation-observer.d.ts
/// <reference path="../../node_modules/mutation-observer" />
declare module "mutation-observer";
Then in your jest file.
import MutationObserver from 'mutation-observer'
(global as any).MutationObserver = MutationObserver
Recently I had a similar problem, where I wanted to assert on something that should be set by MutationObserver and I think I found fairly simple solution.
I made my test method async and added await new Promise(process.nextTick); just before my assertion. It puts the new promise at the end on microtask queue and holds the test execution until it is resolved. This allows for the MutationObserver callback, which was put on the microtask queue before our promise, to be executed and make changes that we expect.
So in general the test should look somewhat like this:
it('my test', async () => {
somethingThatTriggersMutationObserver();
await new Promise(process.nextTick);
expect(mock).toHaveBeenCalledTimes(3);
});

How to pass object argument to browser.execute method?

I am trying to use WebdriverIO's execute method to pass a browser object like this:
describe('reference', () => {
it('test browser', () => {
browser.execute(function(){
console.log('BROWSER:', browser);
});
});
});
But there is an error:
Failed: unknown error: browser is not defined
How can I pass my browser object to a custom function?
Any code inside of execute is run inside the browser itself, meaning that the browser object isn't available.
You can pass the browser object in, but because it won't have access to all the rest of WebdriverIO, I'm guessing it won't work like you're hoping. Regardless, here's the code for that:
browser.execute(function(wdioBrowser){
console.log('BROWSER:', wdioBrowser);
}, browser);
You do have access to the normal DOM though. So if you want to find an element, you can do:
browser.execute(function(){
console.log('Element:', document.querySelector('.my-selector'));
});
If you're looking for more details, I have an 8 minute video on it in my WebdriverIO course (#23 in the list).

are there issues with generating a new wrapper for each test in Enzyme?

I'm doing tests for my React project using Jest + Enzyme.
Currently I would generate a new wrapper for each test in a suite.
example:
it('should render a title', () => {
let wrapper = shallow(<Component />);
expect(wrapper.find('#title')).toHaveLength(1);
});
it('should call closeModal function when clicked', () => {
let wrapper = shallow(<Component />);
wrapper.instance().closeModal = jest.fn();
let targetFunction = wrapper.instance().closeModal;
expect(targetFunction).toHaveBeenCalled();
});
I would like to know whether this is the standard or should I be generating the wrapper in a beforeAll and referencing that one.
I'm interested in this for the potential improvement in speed time. Right now I have 190 tests and they are done in 21.38s.
The problem with beforeAll is that it will use the same instance in all of your test. If you now change the internal state or props of a component in one of you test this can influent the result of the other test.
Normally I would use beforeAll to test different parts of a component without having a generic test like 'renders correct' but multiple small ones like 'renders the title', 'renders the body' and so on, were every test tests a single part of the component, as this will make it easier to find the place where something went wrong if the test fails.

Resources