mounted method giving error message - Nuxt.js, Jest - jestjs

I am new to unit testing and have a question about the mounted method inside the component.
I am testing if the button text is correctly displaying depends on one of the data values, and it passes. However, I have one method in mounted() inside the component and requests API call which is called from nuxt context.
The method is failing and consoling err message from try and catch because looks like it can not find nuxt context inside the test. This is not affecting my test but I wonder if it is fine, or do I need to fix something.
This is my component.
<template>
<button>
{{variations.length > 0 ? 'Select options' : 'add to cart'}}
</button>
</template>
<script>
data() {
return {
variations: [],
}
},
mounted() {
this.getVaridations()
},
methods: {
async getVaridations() {
try {
const variations = await this.$getVatiation.get() // this part is failing and consoling err message from catch
this.variations = variations
} catch (err) {
console.log(err) // consoling as TypeError: Cannot read properties of undefined (reading 'get')
}
},
},
</script>
This is testing
describe('Product.vue', () => {
it('btn display as "Select options" when there is validation', () => {
const wrapper = mount(Product, {})
expect(wrapper.find('.product-btn').text()).toBe('Select options') // This passes
})
})

You can mock any component methods like
import { shallowMount } from "#vue/test-utils";
describe('Product.vue', () => {
it('btn display as "Select options" when there is validation', () => {
const mocks = {
$getVatiation: {
get: () => [] // returns an empty array, change to what you want to return
}
}
const wrapper = shallowMount (Product, {mocks}) // send your mocks as an argument
expect(wrapper.find('.product-btn').text()).toBe('Select options')
})
})

Related

deal with boolean api in js

One of my api response with boolean(with the name: used), my logic is if the response is used will show red_light and green_light if not used.
const red_light = <div className="h-2.5 w-2.5 rounded-full bg-red-700 mr-2"></div>
const green_light = <div className="h-2.5 w-2.5 rounded-full bg-green-400 mr-2"></div>
function lighting(code) {
fetch(`API`)
.then((response) => {
if (!response.ok) {
throw new Error(
`This is an HTTP error: The status is ${response.status}`
);
}
return response.json();
})
.then((actualData) => {
return (actualData.used ? red_light : green_light)
})}
const MembershipLight = (code) => {
return (
lighting(code)
);
};
export default MembershipLight;
but the page gone blank, which part i am doing wrong?
i try to console.log with the actualData, it shows the whole part of the response including used with true/false, but when i console.log("actualData.used"), it shows undefined in the console.
actureData (from postman)
[
{
"used": true,
"create_date": "1644490502",
"update_date": "1666694655"
}
]
You should probably change approach and declare a used state to store the returned boolean value and use conditional rendering to adjust the class accordingly.
Also, as suggested by #KcH, if your response is an array, you should access the element with an index:
import { useState, useEffect } from 'react';
const MembershipLight = (code) => {
const [used, setUsed] = useState(false);
const lighting = () => {
fetch(`API`)
.then((response) => {
if (!response.ok) {
throw new Error(
`This is an HTTP error: The status is ${response.status}`
);
}
return response.json();
})
.then((actualData) => {
if (actualData.length > 0) {
setUsed(actualData[0].used)
}
})
.catch((err) => console.log(err));
}
useEffect(() => {
lighting();
}, []);
return <div className={`h-2.5 w-2.5 rounded-full mr-2 ${used ? 'bg-red-700' : 'bg-green-400'}`}></div>;
};
export default MembershipLight;
Furthermore, you're not returning anything from your lighting function. You should return the result of the fetch. Currently, your MembershipLight returns undefined due to that.

Stencil unable to test mouseenter\mouseleave events using Jest

I built a simple button component using Stencil and assigned 4 events (onMouseDown, onMouseUp onMouseEnter, onMouseLeave), to the button. The component looks like this:
.
.
.
#State() buttonState: string ='disabled';
.
.
.
someInternalLogic(eventName: Events) {
...//just sets a state variable of this.buttonState
}
render() {
return (
<button
onMouseDown={() => this.someInternalLogic(Events.MOUSEDOWN)}
onMouseUp={() => this.someInternalLogic(Events.MOUSEUP)}
onMouseEnter={() => this.someInternalLogic(Events.MOUSEENTER)}
onMouseLeave={() => this.someInternalLogic(Events.MOUSELEAVE)}
>
</button>
);
}
I'm new to testing in general and Jest in particular. I'm having troubles understanding how to test these events synthetically. I've come up with a workaround which works, but is obviously not the way to go.
The workaround:
it('should mouseleave', async () => {
const button = await page.root.shadowRoot.querySelector('button');
const mouseleave = new window.Event("mouseleave", {
bubbles: false,
cancelable: false
});
let mouseleaveBool = false;
button.addEventListener("mouseleave", e=>{
mouseleaveBool = true;
});
await button.dispatchEvent(mouseleave);
await page.waitForChanges();
expect(mouseleaveBool ).toBeTruthy();
});
Instead of dispatching events you can directly call event handlers on your component instance
So for this component
export class TestBtn {
onMouseLeave() {
// do something
}
render() {
return (
<Host>
<button onMouseLeave={() => this.onMouseLeave()}>Test</button>
</Host>
);
}
}
Test can look like this
describe('test-btn', () => {
it('does something on mouse leave', async () => {
// arrange
const page = await newSpecPage({
components: [TestBtn],
html: `<test-btn></test-btn>`,
});
let component = page.rootInstance as TestBtn;
// act
component.onMouseLeave();
// assert
// check if did something
});
});

Receiving a status of 404 when implementing GET request - MERN Stack

I'm trying to display a particular group detail page from the group's list. I have managed to display all the group lists from the backend, but when it comes to displaying each group detail page, I'm receiving a status of 404. I don't understand what the issue is, why it can't find that group on that particular id.
On Backend, I made a getGroup controller:
export const getGroup = async (req, res) => {
// Route parameters are named URL segments that are used to capture the values specified at their position in the URL.
// Deconstructing it so that we can use it. It's like unpacking it.
const { id } = req.params
try {
// finding a group by it's Id
const group = await Group.findById(id)
// Send it in response
res.status(200).json(group)
} catch (error) {
// In case it didn't work out
res.status(404).json({ message: error.message })
}
}
Then Routes:
router.get('/:id', getGroup)
Main Route:
app.use('/groups', groupRoutes)
On Front End, I'm using Axios to create an API.
export const fetchGroup = (id) => API.get(`/groups/${id}`)
Using Redux for state management, so here is my action creator for a particular group.
// Will get a particular group
export const getGroup = (id) => async (dispatch) => {
try {
// Over here we are fetching all the data from api and dispatching it.
const { data } = await api.fetchGroup(id)
dispatch({type: FETCH_GROUP, payload: data})
} catch (error) {
console.log(error.message)
}
}
Reducer for fetching a group:
// Reducers take state and an action as arguments and return a new state in result.
const groups = ( state = { groups: [] }, action ) => {
switch (action.type) {
case FETCH_ALL:
return {
...state,
groups: action.payload
}
case FETCH_GROUP:
// set to group because we are getting a single group
return { ...state, group: action.payload }
default:
return state
}
}
export default groups
My GroupPage.js, where I'm dispatching my functions.
const GroupPage = () => {
const { group } = useSelector((state) => state.groups)
const dispatch = useDispatch()
const { id } = useParams()
useEffect(()=> {
dispatch(getGroup(id))
}, [id])
//if(!group) return null
return (
isLoading ? <div style={{width: 50, height: 50 }}>
<CircularProgressbar value={66} text={66} />
</div> : (
<div>
<Container>
<Top>
<div>
<h1>{group.groupName}</h1>
<p>{group.location}</p>
<p>{group.members}</p>
</div>
</Top>
</Container>
</div>
)
)
}
export default GroupPage
From the above code, if I check for a group:
if(!group) return null
Then I'm getting the error
typeError: Cannot read property 'groupName' of undefined
Not sure what the problem is. I have also tried the data fetching with the loading state, but that didn't resolve the issue either.
Please, any help would be appreciated.

Converting React Functional Component to Hook

While trying to learn full stack development I was trying out this tutorial ( https://www.freecodecamp.org/news/create-a-react-frontend-a-node-express-backend-and-connect-them-together-c5798926047c/ ) on a React-Express-Node basic app. However, it was written using functional components instead of hooks. I'm trying to convert this section to a hook:
constructor(props) {
super(props);
this.state = { apiResponse: "" };
}
callAPI() {
fetch("http://localhost:9000/testAPI")
.then(res => res.text())
.then(res => this.setState({ apiResponse: res }));
}
componentWillMount() {
this.callAPI();
}
with this in the render section:
<p className="App-intro">;{this.state.apiResponse}</p>
I tried this:
const [apiResponse, setApiResponse] = useState();
useEffect(() => {
const fetchApiResponse = async () => {
const result = await (
'http://localhost:9000/testAPI'
);
setApiResponse(result);
console.log("apiResponse " + apiResponse);
};
fetchApiResponse();
});
but the console.log of the apiResponse always shows as undefined. I know I must be doing something wrong but I can't figure it out.
You aren't far off in your attempt.
There are two problems:
Problem 1.
In order to get the same effect as componentWillMount (side note - this is a deprecated method, use componentDidMount or the constructor) you need to tell the useEffect to only run once on mount. To do this you give it an empty array of dependencies.
useEffect(() => {
// do stuff
}, []); // empty array as second argument
By not giving a second argument, the effect will run every single render.
Problem 2.
State updates are asynchronous. This means you cannot console log apiResponse immediately after updating it and expect it to contain the new value.
To get around this, just console.log inside the function body outside of the hook.
Here is a simplified example:
const {useState, useEffect} = React;
const Example = () => {
const [apiResponse, setApiResponse] = useState();
useEffect(() => {
const fetchApiResponse = () => {
const result = 'test';
setApiResponse(result);
// Will not be updated
console.log("wrong: apiResponse ", apiResponse);
}
fetchApiResponse();
}, []);
// Will be updated
console.log("right: apiResponse ", apiResponse);
return <span />
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Testing and mocking fetch in async useEffect and async Redux-Saga

I'm testing a functional component, that use React-Hooks and Redux-Saga. I can pass parameters in URL for the component, because they are a login page component.
My URL i pass is 'localhost/access/parameter', and when this parameter exists, i need to call a async redux saga, and if the fetch is OK, i put the result in redux-store. When the result is on redux-store, i have a useEffect that verify the result and if is OK, i put her in a input.
I can mock the result with axios, but i'm migrating to use only fetch. i mock the fetch, but when i use
mount(component), provided by enzyme, i do not how to await the redux-saga termine the request and useEffect do your job. I put a console log inside a effect, saga and log the input props to see your value prop, but the value is always empty . I tried to use setImmediate() and process.nextTick().
Links i use to write the code: 1,2, 3
I'm using formik, so they pass some props to me.
My component
const Login = ({
setFieldError, errors, response, fetchDomain, location, values, handleChange, handleBlur, setFieldValue, history,
}) => {
useEffect(() => {
async function fetchUrlDomain() {
const { pathname } = location;
const [, , domain] = pathname.split('/');
if (typeof domain !== 'undefined') {
await fetchDomain(domain);
}
}
fetchUrlDomain();
}, [fetchDomain, location]);
useEffect(() => {
if (typeof response === 'string') {
setFieldError('domain', 'Domain not found');
inputDomain.current.focus();
} else if (Object.keys(response).length > 0) {
setFieldValue('domain', response.Domain);
setFieldError('domain', '');
}
}, [response, setFieldValue, setFieldError]);
return (
<input name="domain" id="domain" value={values.domain} onChange={handleChange} onBlur={handleBlur} type="text" />
);
}
const LoginFormik = withFormik({
mapPropsToValues: () => ({ domain: '' }),
enableReinitialize: false,
validateOnBlur: false,
validateOnChange: false,
})(Login);
const mapStateToProps = () => ({ });
const mapDispatchToProps = dispatch => ({
fetchDomain: (value) => {
dispatch(action({}, constants.RESET_RESPONSE_DOMAIN));
dispatch(action(value, constants.FETCH_DOMAIN_REQUEST));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(LoginFormik);
My Saga
export function* fetchDomain(action) {
const url = yield `${mainUrl}/${action.payload}`;
try {
const response = yield fetch(url).then(res => res.json());
yield put(reduxAction(response , constants.FETCH_DOMAIN_SUCCESS));
} catch (e) {
yield put(reduxAction(e, constants.FETCH_DOMAIN_FAILURE));
}
}
My Reducer
case constants.FETCH_DOMAIN_FAILURE:
return { ...initialState, response: 'Domain not found' };
case constants.FETCH_DOMAIN_SUCCESS: {
const { payload } = action;
return {
...initialState,
id: payload.Id,
apis: payload.Apis,
response: payload,
};
}
case constants.RESET_RESPONSE_DOMAIN:
return { ...initialState };
My Test
it('input with fetch only', (done) => {
const mockSuccessResponse = {
Id: 'fafafafa',
Apis: [],
Domain: 'NAME',
};
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
global.fetch = jest.fn().mockImplementation(() => mockFetchPromise);
const wrapper = mount(
<Provider store={store}>
<LoginForm
history={{ push: jest.fn() }}
location={{ pathname: 'localhost/login/Domain' }}
/>
</Provider>,
);
process.nextTick(() => {
const input = wrapper.find('#domain');
console.log(input.props());
expect(input.props().value.toLowerCase()).toBe('name');
global.fetch.mockClear();
done();
});
});
I expect my input have value, but he don't. I tried to use jest-fetch-mock but just don't work, and i want to use native jest methods, no thirty party libraries.
I cannot say what's wrong with your current code. But want to propose different approach instead.
Currently you are testing both redux part and component's one. It contradicts with unit testing strategy when ideally you should mock everything except module under the test.
So I mean if you focus on testing component itself it'd be way easier(less mocks to create) and more readable. For that you need additionally export unwrapped component(Login in your case). Then you can test only its props-vs-render result:
it('calls fetchDomain() with domain part of location', () => {
const fetchDomain = jest.fn();
const location = { pathName: 'example.com/path/sub' }
shallow(<Login fetchDomain={fetchDomain} location={location} />);
expect(fetchDomain).toHaveBeenCalledTimes(1);
expect(fetchDomain).toHaveBeenCalledWith('example.com');
});
it('re-calls fetchDomain() on each change of location prop', () => {
const fetchDomain = jest.fn();
const location = { pathName: 'example.com/path/sub' }
const wrapper = shallow(<Login fetchDomain={fetchDomain} location={location} />);
fetchDomain.mockClear();
wrapper.setProps({ location: { pathName: 'another.org/path' } });
expect(fetchDomain).toHaveBeenCalledTimes(1);
expect(fetchDomain).toHaveBeenCalledWith('another.org');
});
And the same for other cases. See with this approach if you replace redux with direct call to fetch() or whatever, or if you refactor that data to come from parent instead of reading from redux store you will not need to rewrite tests removing mocks to redux. Sure, you will still need to test redux part but it also can be done in isolation.
PS and there is no profit to await fetchDomain(...) in useEffect since you don't use what it returns. await does not work like a pause and that code may rather confuse reader.

Resources