How to write test case using Jest for Yup.isValid function? - reactjs

I am trying to add a unit test to validate the Yup.isValid function, but the after running test case showing error as: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. Even if I am changing the minimum timeout of jasmine same error showing. My function to validate the Yup schema is:
export const validateSchema= (
validationSchema,
data
) => {
return new Promise(async (resolve, reject) => {
await validationSchema
isValid(data)
.then(isFormValid => {
//passing response to method
})
.catch(error => reject(error));
});
};
My test case is:
test("validate Schema",async () => {
let catchFn = jest.fn();
let data= someSampleData;
//data is valid as per the schema
await validationSchema(
validationSchema,
data
)
.then(res => {
//My expected condition
})
.catch(catchFn);
});
Above test case is not going to then where I can put my condition. Same error is coming as I mentioned. How can I fix this issue?

For large schemas it might be nice to use yup's validateAt api to pass in a path to validate for, so fixture data can be more concise.
Jest specs could look something like:
await expect(schema.validateAt(path, fixture)).rejects.toBeTruthy()
it("requires person's name", async () => {
await expect(schema.validateAt('person.name', {})).rejects.toBeFalsy()
await expect(schema.validateAt('person.name', {person: {name: "something"}})).resolves.toBeTruthy()
}
Unit testing yup is interesting, in that a lot of it is configuration, testing that libraries work the way they say they do can be extraneous. However, this does seem to helpful for testing more complex custom schema validation behaviors.

validateSchema uses promise construction antipattern and shows one of the reasons why it's considered an antipattern, new Promise is unneeded construction that is prone to human errors.
The use of async as Promise executor is a mistake that contributes to the antipattern. Promise executor ignores a promise that is returned from async function. resolve is never called, while .catch(error => reject(error)) is no-op. validateSchema returns either rejected or pending promise. If pending promise is returned from a test, this results in a timeout.
It should be:
export const validateSchema= async (
validationSchema,
data
) => {
await validationSchema;
const isFormValid = isValid(data);
await updateFormValidFlag(isFormValid, theFormName); // does it return a promise?
});
};
Mixing await and raw promises is rarely ever needed. Using dummy function in catch in test will result in supressing errors, which is rarely a desirable behaviour.
The test can be:
test("validate Schema",async () => {
let data= someSampleData;
await validateSchema(...);
//My expected condition
});

Just another example, I am mainly check for true values
it( 'should handle validating object', async () => {
const result = await schema.isValid(myObject)
expect( result ).toEqual( true )
} )

Related

Axios request returning promise Object

Not sure why when I log out the price after executing the fetchPrice() method, I am returned a promise object instead of price. Please help :)
async function fetchPrice() {
await axios
.get(assetData(selectedAsset))
.then((response) => {
return response.data.market_data.current_price.eur;
})
.catch((error) => console.log(error.response.data.error));
}
useEffect(() => {
let price = fetchPrice();
console.log("Asset Price: " + price);
let assetTotal = Number(cost) / Number(price);
setAssetAmount(assetTotal);
}, [selectedAsset]);
The problem is how you are handling the await of your function. Normally when working with promises you use either await or a callback, not both. Because await already wait for the promise to resolve or throw an error, you directly use the return inside the function and not in the callback.
async function fetchPrice() {
try {
const price = await axios.get(assetData(selectedAsset));
return price;
} catch (e) {
console.log(error.response.data.error)
}
}
Using return inside the callback doesn't return the object you expect since it is the result of your callback function and not the original function.
Since fetchPrice is an async function it is normal that if you try to print it you will obtain a promise. This won't change even if you correct your function as I told you above. The only way to obtain the object is by awaiting your fetch price inside the useEffect (and by making the useEffect itself async) like this:
useEffect(async () => {
let price = await fetchPrice();
console.log("Asset Price: " + price);
let assetTotal = Number(cost) / Number(price);
setAssetAmount(assetTotal);
}, [selectedAsset]);
However, if you do it, you will obtain the following warning because your use effect use should be synchronous:
Effect callbacks are synchronous to prevent race conditions. Put the async function inside
The final solution? Set state directly inside the callback.
async function fetchPrice(cost) {
try {
const price = await axios.get(assetData(selectedAsset));
setAssetAmount(Number(cost)/Number(price))
} catch (e) {
console.log(error.response.data.error)
}
}
However, be careful of memory leaks when setting a state asynchronously inside a component that can be unmounted.
You need to use either .then() or async/await.
Example of then()
useEffect(() => {
axios.get("https://api.github.com/users/mapbox").then((response) => {
console.log(response.data);
});
}, []);
In above code, we are using .then() so then will be fired when api call is done.
Example of async/await
async function axiosTest() {
const response = await axios.get("https://api.github.com/users/mapbox");
console.log(response.data, "with await");
}
In above code, we are using await to wait until the api call is done.
Here is the Codesandbox to check the difference with live api call

Make Unit Test Wait for Data filled by Asynchronous Fetch

I have a unit test which includes rendering a component that uses useSWR to fetch data. But the data is not ready before the expect() is being called so the test fails.
test("StyleOptionDetails contains correct style option number", () => {
renderWithMockSwr(
<StyleOptionDetails
{...mockIStyleOptionDetailsProps}
/>
)
expect(screen.getByText(123)).toBeInTheDocument();
});
But if I put in a delay setTimeout(), it will pass the test.
setTimeout(() => {
console.log('This will run after 2 second')
expect(screen.getByText(123)).toBeInTheDocument();
}, 2000);
What is the correct way to create a delay or wait for the data?
Although I think you are already doing this, the first thing to note is that you shouldn't be actually fetching any data from your tests-- you should be mocking the result.
Once you are doing that, you will use the waitFor utility to aid in your async testing-- this utility basically accepts a function that returns an expectation (expect), and will hold at that point of the test until the expectation is met.
Let's provide an example. Take the imaginary component I've created below:
const MyComponent = () => {
const [data, setData] = useState();
useEffect(() => {
MyService.fetchData().then((response) => setData(response));
}, []);
if (!data) {
return (<p>Loading...</p>);
}
// else
return (
<div>
<h1>DATA!</h1>
<div>
{data.map((datum) => (<p>{datum}</p>))}
</div>
</div>
);
}
So for your test, you would do
import MyService from './MyService';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
const mockData = ['Spengler', 'Stanz', 'Venkman', 'Zeddmore'];
beforeEach(() => {
jest.spyOn(MyService, 'fetchData')
.mockImplementation(
() => new Promise((res) => setTimeout(() => res(mockData), 200))
);
});
afterEach(() => {
MyService.fetchData.mockRestore();
});
it('displays the loading first and the data once fetched', async () => {
render(<MyComponent />);
// before fetch completes
expect(screen.getByText(/Loading/)).toBeInTheDocument();
expect(screen.queryByText('DATA!')).toBeNull();
// after fetch completes..
// await waitFor will wait here for this to be true; if it doesn't happen after about five seconds the test fails
await waitFor(() => expect(screen.getByText('DATA!')).toBeInTheDocument());
expect(screen.queryByText(/Loading/)).toBeNull(); // we don't have to await this one because we awaited the proper condition in the previous line
});
});
This isn't tested but something like this should work. Your approach to mocking may vary a bit on account of however you've structured your fetch.
I have a similar answer elsewhere for a ReactJS question, Web Fetch API (waiting the fetch to complete and then executed the next instruction). Let me thresh out my solution for your problem.
If your function renderWithMockSwr() is asynchronous, then if you want it to wait to finish executing before calling the next line, use the await command.
await renderWithMockSwr(
<StyleOptionDetails
{...mockIStyleOptionDetailsProps}
/>
)
async is wonderful. So is await. Check it out: Mozilla Developer Network: Async Function

ESLint: Unexpected any. Specify a different type.(#typescript-eslint/no-explicit-any)

I have this line of code which is a wrapper function even though it works fine,
const to = (promise: any) => promise.then((data: any) => data).catch((err: any) => err);
but eslint is prompting the unexpected any error, if i do not include eslint-disable-next-line
ESLint: Unexpected any. Specify a different type.(#typescript-eslint/no-explicit-any)
the function wrapper to() can be used to wrap different login methods for example this function,
const loginWithEmail = async (email:string, password:string) => new Promise((reject) => {
const firebase = getFirebase();
firebase.login({
email,
password,
}).catch((error) => {
reject(error);
return error.message;
});
});
and the end result is placed inside a React Component where it will get the err message and place it into a hook to be displayed.
const handleEmailLogin = async () => {
const err = await to(loginWithEmail(email, password));
setEmailLoginError(err.message);
};
i was wondering if there's a better way to define the function to() without relying on the type any?
The .then is completely superfluous, since it returns the value the Promise resolved with - just use the original Promise.
The .catch turns the rejected value into a resolved value, without doing anything else with it. This is exceedingly strange to do IMO - do you really want to ignore errors ? - but if you do want to do this, then simply omit the type parameter entirely for the err (and it'll be typed as unknown).
The promise should be typed as Promise<unknown> to indicate that it's a Promise that could resolve to any value.
const to = (promise: Promise<unknown>) => promise.catch(err => err);
But it'd probably make more sense to remove the to function entirely.
Your loginWithEmail function looks somewhat broken too. .login returns a Promise, so there's no need to construct another one around it - and the return value of the .catch isn't used elsewhere, so return error.message isn't doing anything.
Consider just
const loginWithEmail = (email:string, password:string) =>
getFirebase()
.login({
email,
password,
});

Jest - how to mock a class in jest

I've been trying to mock a test in jest through the methods that they have on their documentation. By mocking the whole class but I can't seem to get it to work properly.
https://jestjs.io/docs/en/es6-class-mocks
jest.mock('../../../../../src/SubscriptionOrder');
SubscriptionOrder.prototype.createChargebeeSubscription = jest.fn(() => 'response');
const test = new SubscriptionOrder(
'subscription',
[{}],
'errorMethods',
'customerMethods',
);
test.createChargebeeSubscription();
I'd expect this to mock the createChargebeeSubscription method and return the string response but it seems to be returning undeifined
Then this is the piece of code I'm trying to run a test for as well.
const subscriptionOrder = new SubscriptionOrder(
'subscription',
subscriptionRequest,
errorMethods,
customerMethods,
);
const response = await subscriptionOrder.createChargebeeSubscription(token);
this.setState({ successfulSubmit: response });
I want to update the state to the string response but getting undefined instead. so it appears I'm kinda mocking something but just not properly.
You can use spyOn as follows to do the mocking for you. I also recommend that you set up and tear down this spy once you are finished.
So here's a sample piece of code which will do what you want:
describe('createChargebeeSubscription() method behaviour', () => {
let createChargebeeSubscriptionSpy;
let testResponse;
beforeAll(() => {
// Lets create an instance of your class first
const subscriptionOrder = new SubscriptionOrder(
'subscription',
subscriptionRequest,
errorMethods,
customerMethods
);
// Now use a spy to mock the return value from your function
createChargebeeSubscriptionSpy = jest.spyOn(subscriptionOrder, 'createChargebeeSubscription').mockImplementation(() => {
return 'response';
});
// Finally invoke the method being tested
testResponse = subscriptionOrder.createChargebeeSubscription();
});
afterAll(() => {
// Restore the functionality (ie. disable the spy) of your method
createChargebeeSubscriptionSpy.mockRestore();
});
it('verifies that the expected response was returned', () => {
expect(testResponse).toBe('response');
});
});

Stub a function that runs a promise

I've been attempting to write unit tests for a registration page on a new react application
However I am very new to the concept of Sinon stubs/spies and have been having issues with intercepting a function call and forcing a resolve.
This is my initial test:
test('Success notification is displayed if all information is valid', () => {
wrapper.setState({ username: 'test', password: 'Test1234!#', confirmPassword: 'Test1234!#' });
const stub = sinon.stub(Register.prototype, 'signUp').resolves('Test');
wrapper.find('#submitRegistration').simulate('click');
});
onClick runs this event handler: (Simplified)
public onSubmitHandler(e: React.FormEvent) {
// Perform password match check
if (password === confirmPassword) {
this.signUp(this.state.username, password);
} else {
// Set success state
}
} else {
// Set error state
}
}
And finally, Signup:
public signUp(username: string, password: string) {
// Perform Cognito Signup
return Auth.signUp({
password,
username,
})
.then(() => {
// Set success State
})
.catch((err) => {
// Set error state
});
}
How can I intercept the call to signUp and force it down the resolve path, Currently due to the fact i do not configure my AWS Amplify Auth module it catches every time with "No userPool"
Jest provides many ways of mocking out dependencies, primarily there are 2 flavours:
Manual Mocks - These let you control mocks in far more details.
Mock functions - If you need a simple mock for a common problem, translation for example, these are very helpful.
For your case, you will also need to handle Promises, for this reason, you need to follow the advice on testing async behaviour.
For your case, I am assuming:
Auth is a module named 'auth-module'
you just need to setup the mock once and have 1 test data which resolves to success and 1 for failure.
// telling jest to mock the entire module
jest.mock("auth-module", () => ({
// telling jest which method to mock
signUp: ({ password, username }) =>
new Promise((resolve, reject) => {
// setting the test expectations / test data
process.nextTick(
() =>
username === "SuccessUser"
? resolve(userName)
: reject({
error: "failure message"
})
);
})
}));
Note : Adding the snippet even though it will not run, as the formatting seems to be broken with code block.
With a little thought push from dubes' answer I managed to get the following:
const signUpSpy = jest.spyOn(Auth, 'signUp').mockImplementation((user, password) => {
return new Promise((res, rej) => {
res();
});
});
Instead of spying directly onto the function I wrote and call moved it to the Modules function and resolved the promise from there!

Resources