One of my components uses a setTimeout inside ngOnInit, e.g:
ngOnInit() {
setTimeout(() => {
// do some setup stuff using FormBuilder
}, 100);
}
In the unit tests for this component, I need to spy on one of the methods of one of the controls which are built programmatically using FormBuilder, so I am doing this in a beforeEach:
describe('testing something', () => {
beforeEach(() => {
spyOn(component.form.controls.myControl, 'enable');
});
it('does something', () => {
// test stuff
});
});
Before the timeout was added, the test was working perfectly. How can I make the beforeEach wait for the 100 ms timeout in the ngOnInit method?
I've tried adding async and fakeAsync to the outer describe, e.g. like this:
describe('testing something', <any>fakeAsync(() => {
...
}));
or
describe('testing something', async(() => {
...
}));
But in the first case, with fakeAsync I see a message in the test runner Error: Expected to be running in 'ProxyZone', but it was not found., and in the second case it just doesn't even run the test. I also tried wrapping the it methods in fakeAsync but that doesn't do anything to delay the beforeEach
I've tried wrapping the spyOn method inside the beforeEach in a setTimeout and this doesn't appear to have any effect, i.e. the test fails in the same way.
I've also tried using fakeAsync with the beforeEach, like this:
beforeEach(<any>fakeAsync(() => {
tick(100);
spyOn(component.modelForm.controls.myControl, 'enable');
}));
But this doesn't work either. It doesn't result in any errors, but the method I want to spy on doesn't exist yet, even after the tick.
How can I force the beforeEach to wait for the timeout in ngOnInit()? Is it even possible?
The following solution can be adapted for beforeEach if needed
before(() => {
jasmine.clock().install();
})
after(() => {
jasmine.clock().uninstall();
})
it('test case', () => {
spyOn(component.modelForm.controls.myControl, 'enable');
component.ngOnInit();
var timeout = 2000 // 2 seconds
jasmine.clock().tick(timeout);
expect(component.modelForm.controls.myControl.enable).toHaveBeenCalled()
})
I was able to fix this using Jasmine's done callback inside the beforeEach, like this:
beforeEach((done) => {
component.ngOnInit();
setTimeout(() => {
spyOn(component.modelForm.controls.myControl, 'enable');
done();
}, 100);
});
Seems so intuitive!
Related
describe("Share Link", () => {
beforeEach(() => {
cy.generateLink().then(response => {
let url = response.meeting_shared_link.split("/");
cy.wrap(url[url.length - 1]).as("link");
});
describe("when I turn on link sharing", () => {
// This works. Changing it to a before hook breaks it.
beforeEach(() => {
cy.get("#link").then(link =>
cy.toggleLinkSharing({ link: link })
);
});
});
I am currently generating an alias - #link - in my first beforeEach hook, and then accessing it in the next beforeEach hook which is nested in a describe.
My problem is that I need the latter hook to be a before, rather than a beforeEach.
When I modify it to a before hook - it is unable to find the alias "link". Why?
I understand that aliases are cleared between each test, hence the need for the first beforeEach hook - but why isn't it available inside a before hook?
Edit:. I think, it may be because the latter defined before hook triggers before the first beforeEach - in which case, the alias didn't yet exist. If this is the case - it's not intuitive. The before hook should only fire after the beforeEach since it's nested in another describe.
You can change the outer beforeEach() to before(), and preserve the alias between tests with beforeEach(function() { cy.wrap(this.link).as('link') })
It works because this.link is not cleared between tests, even though the alias #link is cleared.
describe("Share Link", () => {
before(() => {
cy.wrap('my-url').as("link");
console.log('Outer before')
});
describe("when I turn on link sharing", () => {
beforeEach(function() { cy.wrap(this.link).as('link') }) // preserve the alias
before(() => {
cy.get("#link").then(link => {
console.log('Inner before', link)
})
})
it('1st test', () => {
cy.get('#link').then(link => console.log('1st test', link))
})
it('2nd test', () => {
cy.get('#link').then(link => console.log('2nd test', link))
})
})
})
Console output
Outer beforeEach
Inner before my-url
1st test my-url
2nd test my-url
You can use this.link and try out.
describe("Share Link", () => {
beforeEach(() => {
cy.generateLink().then(response => {
let url = response.meeting_shared_link.split("/");
cy.wrap(url[url.length - 1]).as("link");
});
describe("when I turn on link sharing", () => {
// This works. Changing it to a before hook breaks it.
beforeEach(() => {
cy.toggleLinkSharing(this.link)
});
});
Reference here:
https://docs.cypress.io/guides/core-concepts/variables-and-aliases#Sharing-Context
I have a function like this:
join(): void {
this.working.value = true;
if (this.info.value) {
axios.get('/url')
.then((result: ResultStatus) => {
this.result = result;
})
.catch((reason: AxiosError) => {
this.showError(AjaxParseError(reason));
})
.finally(() => {
this.working.value = false;
});
}
}
and I want to write some unit tests for this. The first unit test I want to write is to test that 'this.saving' is set to true so that I ensure my UI has a value it can use to show a loading indicator.
However, when I use jest to mock axios, jest resolves the axios promise immediately and I don't have a chance to test what happens before the then/finally block is called. Here is what my unit test code looks like:
import axios from 'axios';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
import successResponse from './__json__/LeagueJoinInfoSuccess.json';
describe('constructor:', () => {
let vm: classUnderTest;
beforeEach(() => {
vm = new classUnderTest();
mockedAxios.get.mockResolvedValue({ data: successResponse }); // set up the response
vm.join(); // the function under test
});
it('should set working true before calling the server to join', () => {
expect(vm.working.value).toBeTruthy();
});
it('should set working false after calling the server responds', async () => {
await flushPromises();
expect(vm.working.value).toBeFalsy();
});
});
The first expect statement is always false because the finally block is run before I have a chance to do an await flushPromises(); so the working value is always false.
Is there a convenient way to get jest's mock of axios to wait before resolving its promise?
UPDATE: Now here is a really strange thing: If I move the contents of BeforeEach into each of the tests, then it behaves the way that I am hoping it would behave. I guess I will open an issue over at jest and ask them what's going on.
I have a solution for you to create a promise as response, however, we're not gonna resolve it in the 1st test case to keep you test loading state then resolve it in the 2nd test as following:
describe('constructor:', () => {
let vm: classUnderTest;
// Resolve function
let resolveFn: (value?: unknown) => void
beforeEach(() => {
vm = new classUnderTest();
const promise = new Promise(resolve => {
// hold the resolve function to call in 2nd test
resolveFn = resolve;
});
mockedAxios.get.mockImplementation(() => promise);
vm.join(); // the function under test
});
it('should set working true before calling the server to join', () => {
expect(vm.working.value).toBeTruthy();
});
it('should set working false after calling the server responds', async () => {
// resolve the pending promise
resolve({
data: successResponse,
});
// I think you would wait the promise resolved
await new Promise(resolve => setTimeout(resolve));
expect(vm.working.value).toBeFalsy();
});
});
I'm using Jest to unit test a component that makes an Axios call in it's mounted() method. I'm mocking the Axios call so it returns a promise so the API call is not actually made. But the problem seems to be that because Axios is asynchronous, how do I tell my test to wait for the Axios call (even the fake one) to complete before running my expectations?
This does not work:
it('calls auth api', () => {
spyOn(axios, 'get').and.callFake(() => Promise.resolve().then(() => authResponse));
wrapper = shallowMount(App, {
localVue,
});
expect(axios.get).toHaveBeenCalledWith('api/auth');
// The "status" data is set in the axios callback. But this fails.
expect(wrapper.vm.status).toBe('AUTHORIZED');
});
If I wrap it in a timeout, it does work. I think I've read this is because the timeouts are always called after promises are resolved or something?
it('calls auth api', () => {
spyOn(axios, 'get').and.callFake(() => Promise.resolve().then(() => authResponse));
wrapper = shallowMount(App, {
localVue,
});
expect(axios.get).toHaveBeenCalledWith('api/auth');
setTimeout(() => {
expect(wrapper.vm.status).toBe('AUTHORIZED');
}, 0);
});
Is there a better way to do this?
Thanks!
Untested, but does the following not work?
const flushPromises = require('flush-promises');
it('calls auth api', async () => {
spyOn(axios, 'get').and.callFake(() => Promise.resolve().then(() => authResponse));
wrapper = shallowMount(App, {
localVue,
});
await flushPromises(); // <<
expect(axios.get).toHaveBeenCalledWith('api/auth');
});
(You will need to add https://www.npmjs.com/package/flush-promises (or write your own, it is about 4 lines of code, returning a resolved promise)
As mentioned in my previous question about mocking, I am new to Jest and testing and I seem to be getting some curveballs.
This time around I am having trouble mocking shelljs in my CLI application.
Automocking jest.mock('shelljs'); didn't work and errored as:[TypeError: shell.exec is not a function]
So I went ahead and tried to use mockImplementation()
jest.mock('shelljs', () => {
return jest.fn().mockImplementation(() => {
return {
exec: () => {}
};
});
});
To my surprise I am still getting the same error message
Any pointers would be much apprecieted.
UPDATE 08/04/2020:
As per Teneff's reply below, the mocking works fine with:
jest.mock('shelljs', () => {
return {
exec: jest.fn()
};
});
Now I'm getting timeouts as my call of shell.exec() is async and have a callback that resolves my promise.
My goal is to mock shell.exec() to just resolve the promise, but it goes into waiting around and Jest times out.
Taking onboard Teneff's answer I realised that the timeout happens, because I mocked shell.exec successfully however I have used it's async version exec(command [, options] [, callback]) so I tried first logging out the arguments and it worked.
All was left to do to call the callback and voila, my test works.
jest.mock('shelljs', () => {
return {
exec: jest.fn((_, __, callback) => callback())
};
});
As you're using shell as an object with .exec property your jest.mock factory function should return an object with exec property
jest.mock('shelljs', () => {
return { exec: jest.fn() }
});
Teneff answer worked for me.
But as I wanted to mock different shell responses I improved it like that :
const shelljs = require('shelljs');
jest.mock('shelljs');
describe('Run the test suite', () => {
test('it should ...', async () => {
shelljs.exec = jest.fn().mockImplementation(() => ({ code: 0 }));
...
expect(...);
});
test('it should ...', async () => {
shelljs.exec = jest.fn().mockImplementation(() => ({ code: 1 }));
...
expect(...);
});
});
I'm trying to test promises-chain sequence with Jest:
someChainPromisesMethod: function() {
async()
.then(async1)
.then(async2)
.then(result)
.catch(error);
}
While testing single promise is good documented not sure what is a proper way (not sure what to do TBO) to test this kind of chain. Let's assume all asyncs are mocked and just resolves promises (Promise.resolve) in theirs body.
So I need something that will test whole sequence.
You can use jest.fn() to mock implementation and check what the function has been called with and return what you want. You need to mock all async functions you have in your function and return what you want.
e.g.
async = jest.fn(() => {
return Promise.resolve('value');
});
async1 = jest.fn(() => {
return Promise.resolve('value1');
});
async2 = jest.fn(() => {
return Promise.resolve('Final Value');
});
You can use this in your test as
it('should your test scenario', (done) => {
someChainPromisesMethod()
.then(data => {
expect(async1).toBeCalledWith('value');
expect(async2).toBeCalledWith('value1');
expect(data).toEqual('Final Value');
done();
});
});
However, I would flatten your chain and test them separately if you have logic in them, that way you can test all possibilities easily.
Using done does not fix the issue, it will give you a false positive test. If for any reason an expectation fails, your test will timeout and you'll not have the real result.
The right solution is to return your Promise, so Jest will be able to evaluate the expect result correctly.
Following the #grgmo example, a better approach could be:
it('should your test scenario', () => {
return someChainPromisesMethod()
.then(data => {
expect(async1).toBeCalledWith('value');
expect(async2).toBeCalledWith('value1');
expect(data).toEqual('Final Value');
});
});