Jest beforeEach/afterEach just the blocks in this scope? - testing

I want to do something like this:
beforeEach(() => {
populateDatabase();
});
afterEach(() => {
clearDatabase();
});
describe("Create and update user test suite", () => {
let user;
it("Post a new user returns a user", async () => {
const initUser = {/*...*/};
user = await createUser(initUser);
//expect...
});
it("Modify the user returns the modified user", async () => {
user = await modifyUser({...user, ...{/*...*/});
//expect...
});
});
describe ("Create and update business test suit", () => {
let business;
//it...
});
That is, I want to retain the database state from test to test within the describe block, and then clear it down at the end the describe block.
But the way this code would work is that it would init and clear down after each test, which isn't what I want.
I could just put all the expects into one single test, but then I'd lose visibility of exactly which part of the test is failing.
What would the best way to handle this be?

To reinitialize your database after upon the completion of each describe block, check the beforeAll and afterAll methods.
Placing these methods inside the describe block will scope them to that specific block

Related

Cypress spy on multiple calls of the same method

I'm trying to check that a method was not called again after a certain action.
My test:
it('if query is less than 3 symbols, api call is not made', () => {
cy.spy(foo, 'bar').as('bar');
cy.get('input').type('12').then(() => {
cy.get('#bar').its('callCount').then(res => {
expect(res).to.eq(1); // a basic check after mounted hook
});
});
});
My component:
async mounted(): Promise<void> {
await this.foo.bar();
}
async getSearchResults(): Promise<void> {
if (this.searchQuery.length < 3) {
return;
}
await this.foo.bar();
}
The problem is that bar was already called on mount, and it could have been called multiple times before, if query length was valid. I was thinking about saving bar's callCount to a variable and checking it after call, but that looks ugly. Kinda stuck here, any ideas are welcome.
It's not an issue. The call count is started at the point you set up the spy, not when the component is mounted.
Try this:
const foo = {
bar: () => console.log('bar called')
}
it('starts with a clean callcount', () => {
foo.bar() // make a call
cy.spy(foo, 'bar').as('bar'); // callCount === 0 on setup
cy.get('#bar')
.its('callCount')
.should('eq', 0) // passes
});
Even if you have some callcount from another test, you can always reset it before the current test:
it('allows reset of spy callCount', () => {
cy.spy(foo, 'bar').as('bar'); // callCount === 0 on setup
foo.bar() // make a call, count is now 1
cy.get('#bar').invoke('resetHistory') // remove prior calls
cy.get('#bar')
.its('callCount')
.should('eq', 0) // passes
});
I believe you can get the initial call count, and then wrap your test in that.
it('if query is less than 3 symbols, api call is not made', () => {
cy.spy(foo, 'bar').as('bar');
cy.get('#bar').its('callCount').then((initRes) => {
cy.get('input').type('12').then(() => {
cy.get('#bar').its('callCount').then(res => {
expect(res).to.eq(initRes); // a basic check after mounted hook
});
});
});
});
You would probably want to do a test that this would fail, to make sure that Cypress is getting '#bar' again.

Jasmine Spy called incorrectly

I'm running into an interesting problem. I have set up a jasmine spy on an event listener that I attach and detach during the lifecycle of the component (this is in lit element by the way). On the connected callback I attach it like this:
getPositionEvent = this.getPosition.bind(this);
connectedCallback() {
super.connectedCallback();
window.addEventListener('resize', this.getPositionEvent, true);
}
I later detach it like this:
disconnectedCallback() {
window.removeEventListener('resize', this.getPositionEvent, true);
}
I can see during testing of the code that the attachment works here:
let getPositionEventSpy: jasmine.Spy;
beforeAll(() => {
(code setting up component)
getPositionEventSpy = spyOn(component, 'getPositionEvent');
}
beforeEach(async () => {
component.connectedCallback();
await component.updateComplete;
getPositionEventSpy.calls.reset();
})
it('should include an event listener for "resize"', async () => {
window.dispatchEvent(new Event('resize'));
await component.updateComplete;
expect(getPositionEventSpy.calls.count()).toEqual(1);
})
The problem comes when I try to test that the event listener is detached. I put this test in a separate describe block where I initiate a the disconnectedCallback function which should remove the event listener and then test that the spy has not been called when I dispatch the event:
describe('disconnection', () => {
beforeAll(() => {
component.disconnectedCallback();
getPositionEventSpy.calls.reset();
})
it('should remove the "resize" event listener', async () => {
expect(getPositionEventSpy.calls.count()).toEqual(0);
window.dispatchEvent(new Event('resize'));
await component.updateComplete;
expect(getPositionEventSpy).not.toHaveBeenCalled();
})
})
In this case the test fails meaning the getPositionEventSpy has been called. In trying to understand what's happening I added a console.log("getting position") statement in the getPosition() function. When I run the test for removing the event listener the console log statement doesn't get run, so I believe that the removal of the eventlistener is actually successful. So why does the spy count increase? Does anybody know?
try spy.resetHistory()
beforeAll(() => {
component.disconnectedCallback();
getPositionEventSpy.resetHistory();
})

How to share response data across multiple suites?

Let's say we have the following suite:
describe('Devices', () => {
describe('Master Data Set-Up', () => {
it('should create the device if necessary', () => {
cy.createDevice()
its('body.id')
.as('deviceId');
});
});
describe('Test Suite 1', () => {
it('should allow to send data to device', () => {
cy.get('#deviceId').then((deviceId) => {
cy.sendData(deviceId, 'Some Data');
});
});
});
});
So, we have a set up suite that creates master data. This is a simplified version, actually it contains a couple of it specs and I'd like to keep it like that because it's better to read in the Cypress output.
Then, there is the actual test suite that want's to use data that has previously been created. In this case a server generated id that should be used for another REST call.
This is assuming, that cy.createDevice and cy.sendData are custom commands available that internally use cy.request.
When running that, cy.get('#deviceId') fails because aliases are not shared across describe blocks AFAIK. I tried to use let deviceId but it's undefined as it is not yet available when the test specs are processed.
What is a proper way to do this?
I believe this will be better solution, as cypress is asynchronous so it's better to write it on file and read it
describe('Devices', () => {
describe('Master Data Set-Up', () => {
it('should create the device if necessary', () => {
cy.createDevice()
......
cy.writeFile('deviceId.txt', body.id)
});
});
describe('Test Suite 1', () => {
it('should allow to send data to device', () => {
cy.readFile('deviceId.txt').then((device_id) => {
cy.sendData(device_id, 'Some Data');
})
});
});
});
Upvote for #ArekKhatry's idea, but to be safe I would obtain the the id in a before(). If you ever run tests in parallel, grabbing data from one test to use in another would be flaky.
Note that running cy.createDevice().its('body.id') in the before() still gives you the same test coverage as running inside it(), i.e it tests that the request succeeds and the return value has an id.
The file should be written to cypress/fixtures, otherwise it will write to the project root causing untidy pollution of the file structure.
Also, the id is returned from cy.request() as a number, but must be stringifyed in order to write to a text file.
Here's my variant
describe('Devices', () => {
before(() => {
cy.createDevice()
.its('body.id')
.then(id => {
cy.writeFile('cypress/fixtures/deviceId.txt', id.toString());
cy.log(`Created device: ${id}`);
});
});
describe('Test Suite 1', () => {
it('should allow to send data to device', () => {
cy.fixture('deviceId') // can use simpler cy.fixture here
.then(device_id => { // returned as a string here
const id = parseInt(device_id); // may need to parse to number?
cy.sendData(id, 'Some Data');
})
});
});
});
Ok, so first thanks to Aloysius and Arek for their answers. But I had the gut feeling that there must be some easier way to do this that writing an Id to a file.
As I mentioned before, I had issues with my first attempt to use a global variable:
I tried to use let deviceId but it's undefined as it is not yet
available when the test specs are processed.
I really wanted to understand, why this did not work and did some console debugging.
I added a console log:
describe('Devices', () => {
console.log('Loading test suites...')
(...)
});
When running the tests, I saw the log output twice, once after the first describe block where the device id was stored and then a second time after the master data was written.
Actually, I found out that this issue was cause by the following known Cypress issue:
https://github.com/cypress-io/cypress/issues/2777
After setting the baseUrl, it actually works:
describe('Devices', () => {
let deviceId;
before( () => {
Cypress.config('baseUrl', Cypress.env('system_url'))
cy.visit('/');
})
describe('Master Data Set-Up', () => {
it('should create the device if necessary', () => {
cy.createDevice()
.its('body.id')
.then((id) => {
deviceId = id;
});
});
});
describe('Test Suite 1', () => {
it('should allow to send data to device', () => {
cy.sendData(deviceId, 'Some Data');
});
});
});

Overriding Cypress' before and after methods

We are using Cypress.io to build our automation suite. We have a requirement to seed our database before every test and to clear the data afterward. This could be done like below.
describe('Our test suite', function() {
before(function () {
//loadDbSeed is a custom command that will load the seed file based on the spec file
seed = cy.loadDbSeed()
cy.task('seed:db', seed)
})
it('should be true', function() {
//Some test with some action followed by an assertion
cy.visit('/some-page')
cy.get('[data-cy="identifier"]')
.click()
expect(true).to.equal(true)
})
after(function () {
// clearDb is a custom command that will clear out the DB.
// We are still debating if we must clear the DB after the tests.
// But we might still need to do some common actions for the entire suite after
cy.clearDb()
})
})
The problem we see is that the same before and after operations will be required for all our test suites. So we would like to override these methods so that our tests are something like this.
describe('Our test suite', function() {
before(function () {
// DB seeding is done automatically
// Write only custom before steps required for this test
})
it('should be true', function() {
//Some test with some action followed by an assertion
cy.visit('/some-page')
cy.get('[data-cy="identifier"]')
.click()
expect(true).to.equal(true)
})
after(function () {
// DB clearing is done automatically
// Write only custom after steps required for this test
})
})
How do we achieve this? I have been digging around in the Cypress code and haven't found anything obvious.
If you look at the Cypress docs, using after isn't recommended - Cypress Docs. I'd caution against setting data globally, will you really need it for every test? If you need to enter data on a per-test basis at some point, will that conflict with this global data? You could do something like this on a per test basis:
describe('Our test suite', function() {
beforeEach(function () {
cy.then(async () => {
await MyDatabaseService.resetdata()
await MyDatabaseService.createSomeData()
});
});
it('should be true', function() {
//Some test with some action followed by an assertion
})
})
I've also had to nest some tests as follows when specific tests needed specific data (sorry if some of the formatting here is a bit out, hopefully it'll make sense!):
describe('Our test suite', function() {
beforeEach(function () {
cy.then(async () => {
await MyDatabaseService.resetdata()
await MyDatabaseService.createSomeData()
});
});
it('should be true', function() {
//Some test with some action followed by an assertion
});
describe('These tests need more data, this is a nested describe', function () {
before(function () {
cy.then(async () => {
await MyDatabaseService.addSomeMoreData()
});
it('this test uses the extra data', function () {
// Do some funky tests here
});
});
});
})
For the second tests above, the test will run all three database actions.
Upshot is, if you clear data before you run your tests then it makes things much clearer.
I hope that helps. I'm new to Cypress myself and it can be hard to drop that (bad?) habits we've used for some time in Selenium!

How to test `state` changes in stage in jest and enzyme when simulate?

How do we test changes in state variable in a stage which means I want to test not the final result but also the result in between.
Below is the code I want to test.
onLogin = () => {
this.setState({ isLoggingIn: true }, () => {
try {
} catch (err) {
// Alert.alert(getString('ERROR'), err.message);
} finally {
this.setState({ isLoggingIn: false });
}
});
}
Below is my test code.
it('should call onLogin callback', () => {
const spy = jest.spyOn(wrapper.instance(), 'onLogin');
const loginBtn = wrapper.find('#login');
loginBtn.props().onPress();
expect(spy).toHaveBeenCalled();
expect(wrapper.instance().state.isLoggingIn).toEqual(false);
// Attempt => Below I've tried
// wrapper.update();
// expect(wrapper.instance().state.isLoggingIn).toEqual(true);
// wrapper.update();
// expect(wrapper.instance().state.isLoggingIn).toEqual(false);
wrapper.instance().state.isLoggingIn is always false. How do I monitor this changes to false then true?
How do we test changes in state variable
We don't. Doing this would:
make tests less maintainable
hard-code state structure in the tests: so unit tests would fail even if everything is fine(more needless work)
still keep us unsure if it works at all.
Just test render() result and interact with everything it returns. This way you would test what really matters.