React | Unit testing how to test methods inside the succesful AJAX call? - unit-testing

Currently, In my react app, I have an ajax call method that has setState if the ajax call is successful, I already mocked the api response, it seems that the method won't execute anything else other than returning the response
For example,
// This is the ajax method
getProductData(productId) {
// I already successfully mocked the response
getProductById(productId).then(productResponse => {
// I am trying make sure that the state is being updated with response data
this.setState({
product: productResponse.data
})
});
}
This is my test
test('Make sure my state is being updated with response data', async () => {
const component = shallow(
<MyComponent />
);
component.instance().getProductData(1);
// This is where my test failed, as it's not updating anything
expect(component.state().product).to.equal('SOMETHING');
});
Tried using setTimeout to asynchronously test the method, suggested by Jonah Pereira
test('Make sure my state is being updated with response data', async () => {
const component = shallow(
<MyComponent />
);
component.instance().getProductData(1);
// Using settimeout still not solving the problem,
setTimeout(() => {
expect(component.state().product).to.equal('SOMETHING');
// Not even this, that means the test totally ignored the setTimeout
expect('abc').to.equal('cba');
}, 0)
});

Usually if I test my react components which involve AJAX calls i enclose my methods inside a setTimeout
setTimeout(() =>
{
expect(component.state().product).to.equal('SOMETHING');
}, 0);

Related

Stenciljs unit testing getting error : ReferenceError XMLHttpRequest is not defined

I'm trying to create units test for my stencil js component, in the compnentWillLoad() method it will do an HTTP request (using rxjs).when I'm run the test getting error ReferenceError: XMLHttpRequest is not defined.But when removing the HTTP request from the componentWillLoad() method test passed.
My test as below,
it('should render my component', async () => {
const page = await newSpecPage({
components: [MyComponent],
html: `<my-component></my-component>`,
});
expect(page.root).toEqualHtml(`<my-component></my-component>`);
});
I'm getting error ReferenceError: XMLHttpRequest is not defined
XMLHttpRequest is indeed not defined in the virtual DOM context that is created when you use newSpecPage.
The best solution for you is probably to write this as an E2E test instead, using newE2EPage, which is more suited for complete end-to-end testing because it runs in a real browser context where XMLHttpRequest will be available.
it('should render', async () => {
const page = await newE2EPage({ html: '<my-component></my-component>' });
const myComponent = page.find('my-component');
expect(myComponent).toHaveClass('hydrated');
});
"Spec Page" testing is rather meant for unit testing components that work stand-alone. If your goal is to actually unit-test your component and you just want to be able to instantiate your component but you don't actually need the request to succeed for testing, then you can also use the Build context from Stencil:
import { Build, ... } from '#stencil/core';
export class MyComponent {
componentWillLoad() {
if (!Build.isTesting) {
// make the request
}
}
// ...
}
I had similar troubles with Stencil, Jest and XMLHttpRequest.
First, make sure you call
new window.XMLHttpRequest()
instead of simply calling
new XMLHttpRequest()
This seems to be neccessary when using jsdom and may already resolve your issue.
It didn't resolve mine though, since I wanted to make sure there are no real API calls going on. So I tried to mock XMLHttpRequest. However, I ran into other issues while building the mock and finally decided to refactor my code to use Fetch API instead of XMLHttpRequest which seems to be better supported by Stencil.
You can easily mock fetch using jest
export function mockFetch(status, body, statusText?) {
// #ts-ignore
global.fetch = jest.fn(() =>
Promise.resolve({
status: status,
statusText: statusText,
text: () => Promise.resolve(JSON.stringify(body)),
json: () => Promise.resolve(body),
})
)
}

spyOn question regarding function that return promise

This is a rather general question about spyOn when writing unit tests with functions that return promises in a Vue component.
The way I write test is the following:
// Basically this function gets data from a service and sets some data in the component.
function getSomething() {
ServiceX.getSomething().then(response =>
this.x = response.x
)
}
the test:
describe('test', () => {
beforeEach() => {
vm = shallowMount(VueComponent)
spyOn(serviceX, 'getSomething).and.returnValue(promsie.resolve(data));
}
it('should set X', () =>{
vm.getSomething()
expect(vm.X).toBe(X);
})
}
The issue is, when I do the test this way, the variable X is not set yet, but if I do the "it" statement async and await for the getSomething() method it works.
I wonder if there is another way to do this.
Because your original method returns a promise, and your spy is also returning a promise (even an already resolved) you should use the then or the async await as you commented in the question.
So, the other way of doing is:
it('should set X', (done) => {
vm.getSomething().then(() => {
expect(vm.X).toBe(X);
done();
});
})
Using the done parameter from jasmine to notify this unit test is asynchronous and will complete when this callback is called.
i know it's a little bit too late and you probably already found a solution, but in case you didn't i think that the solution i will propose will work for you. You can use the combination of fakeAsync and tick to test your asynchronous function, bellow is my solution.
describe('test', () => {
...
it('should set X', fakeAsync(() =>{
vm.getSomething();
tick();
expect(vm.X).toBe(X);
}));
...
}

How to unit test Vue component that uses Axios (or other async update)?

I have a Vue component/view that performs an API request using Axios and updates the component data with the response. I'm using Moxios to mock the Axios request in unit tests.
I tried using Vue.nextTick to postpone assertion of the updated data, but the component has not updated at that point yet. If I add a delay, the assertion works correctly:
setTimeout(() => {
expect(wrapper.text()).toMatch('Updated text')
done()
}, 500)
However this is bad practice, slows down the tests and is a race condition.
Is there some kind of assertion check that would be called every time a component updates? Essentially, I'm looking for something like:
Vue.eventually(() => {
expect(wrapper.text()).toMatch('Updated text')
done()
})
A generic (non-Vue-specific) workaround:
const test = () => {
try {
expect(wrapper.text()).toMatch('Updated text')
done()
} catch (e) {
setTimeout(test, 1)
}
}
setTimeout(test, 1)
However, any failures from the expect are ignored and the test times out without any message if failing.

Jest how to check async response

I'm new to Jest and React so this should be a very simple question to answer... I have an api-endpoint I'd like to check that I can hit. I picked Axios as a client to try this and created the following test:
describe('Api Tests', () => {
it('can perform an axios request', () => {
console.log('Here goes!');
const resp = axios.get('api-endpoint');
console.log(resp);
expect(resp).toBeDefined();
console.log('Done...');
});
});
Thankfully, the test passes, but with the following output:
PASS src\api\api.test.js
Api Tests
√ can perform an axios request (39ms)
console.log src\api\api.test.js:15
Here goes!
console.log src\api\api.test.js:23
Promise { <pending> }
console.log src\api\api.test.js:25
Done...
How do I test a simple request (WITHOUT MOCKING) so that I can get back a response that I can then interrogate?
Since axios.get returns a promise you should instruct Jest to wait for the response to return.
it('can perform an axios request', async () => {
const resp = await axios.get('api-endpoint');
expect(resp).toBeDefined();
});
or without async functions:
it('can perform an axios request', () => {
return expect(axios.get('api-endpoint')).resolves.toBeDefined();
});
I have nice and easy method , maybe you will like it too. Just keep it simple.
`import axios from 'axios';
axios.get('url goes here')
.then(response =>{console.log(response)})`
Maybe find helpful to you.

Angular2 testing logic before .subscribe

I am currently writing unit tests for one of my components. In particular, I have login(): void function. Here's the simplified logic:
login(): void {
this.showSpinner = true;
this.userService.login(loginData)
.subscribe(result => {
this.showSpinner = false;
}
)
}
I am struggling to write a test that checks that showSpinner property gets set to true before calling the userService.login.
Here's my test:
it('should display the spinner when the form is being saved',
inject([TestComponentBuilder], fakeAsync((tcb: any) => {
createComponent(tcb).then((fixture:ComponentFixture<any>) => {
fixture.componentInstance.login();
expect(fixture.componentInstance.showSpinner).toBe(true);
tick();
});
})));
});
And this test fails, because .subscribe gets resolved / run immediately (i tried commenting out this.showSpinner = false in my component, and the test passed).
In my userService mock, I have the following, for the login method mock:
this.loginSpy = this.spy('login').andReturn(Observable.of(this));
Where this is mockUserService.
I am confident that I am mocking userService and specifically the login method on the userService correctly, as I have other tests for this component that behave correctly.
I have also tried returning Observable.of(this).delay(1) from my spy and then calling tick(1) in my test. However that results in inconsistent behaviour in that sometimes my tests pass, but other times i get an error saying:
Error: 1 periodic timer(s) still in the queue.
How can I test the logic that precedes .subscribe()?
After more consideration I have realized that my current code does not abide by the single responsibility principle. This thought came from the fact that everyone is always repeating that you should "Refactor hard to test code".
With that in mind, I have moved all the logic that needed to be done before the call to userService.login is being made - into its own separate function. Which essentially results in:
login():void {
this.userService.login(this.loginData)
.subscribe(result => {
this.showSpinner = false;
});
}
formSubmit(): void {
this.showSpinner = true;
this.login();
}
This logic is now much easier to test.
HOWEVER we need to remember to add a spy on our login() method when we are testing formSubmit(), as if we don't, formSubmit() will simply make a call to login(), which will again complete synchronously and we will have the same problem. So my new and final test for this feature is:
it('should display the spinner when the form is being saved',
inject([TestComponentBuilder], fakeAsync((tcb: any) => {
createComponent(tcb).then((fixture:ComponentFixture<any>) => {
var loginSpy = spyOn(fixture.componentInstance, 'login');
fixture.componentInstance.formSubmit();
expect(fixture.componentInstance.showSpinner).toBe(true);
});
})));
});