I am trying to fill a Text Input and verify that the text is filled correctly, accessing the component and getting its value.
I have succeeded in doing so, but without using redux, ie using the native states of react-native. this.state.
Component Code:
//inside constructor
this.state = {
email: ''
}
<TextInput value={this.state.email} onChangeText={(text) => {
console.log('Here change email text!!! ==> ', text);
this.setState({
email: text
})
}} />
Test File Code:
import LoginScreen from '../../App/Containers/LoginScreen' // => connected component.. exported with `export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen)`
import configureStore from 'redux-mock-store'
import { shallow } from 'enzyme'
import Actions, { reducer, INITIAL_STATE } from '../../App/Redux/Reducers/UserReducer'
const initialState = {
user: {
email: 'mockState email',
password: '',
requesting: 0,
userData: null,
loginFinish: false,
errorMessage: null
}
}
const mockStore = configureStore([]);
let store = mockStore(initialState);
const wrapper = shallow(
<LoginScreen/>,
{ context: { store: store } },
);
test('>>>>> LoginScreen component renders correctly', () => {
expect(wrapper.dive()).toMatchSnapshot();
});
test('>>>>> Login button Press', () => {
let render = wrapper.dive();
const textInputProps = render.find('TextInput'); //getting text input from render
console.log(`textInputProps.getNode(1).props.value BEFORE ====>`, textInputProps.getNodes()[0].props.value);
textInputProps.first().simulate('changeText', 'My new value'); // executing onChangeText inside render of component
const textInputProps2 = render.find('TextInput'); //getting text input again for check changes
console.log(`textInputProps2.getNode(1).props.value====>`, textInputProps2.getNodes()[0].props.value);
const state = store.getState(); //verifying internal `initialState`.. NOT CHANGES
console.log('state ===> ', state);
});
I have relied on this link
Running test logs
yarn test v0.24.6
$ jest
PASS Tests/Containers/loginScreenTest.js
✓ >>>>> LoginScreen component renders correctly (282ms)
✓ >>>>> Login button Press (33ms)
console.log Tests/Containers/loginScreenTest.js:60
textInputProps.getNode(1).props.value BEFORE ====>
console.log App/Containers/LoginScreen.js:124
Here change email text!!! ==> My new value
console.log Tests/Containers/loginScreenTest.js:67
textInputProps2.getNode(1).props.value====> My new value => (!!!WORKS!!!)
console.log Tests/Containers/loginScreenTest.js:86
state ===> { user:
{ email: 'mockState email',
password: '',
requesting: 0,
userData: null,
loginFinish: false,
errorMessage: null } }
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 1 passed, 1 total
Time: 2.337s, estimated 3s
Ran all test suites.
✨ Done in 3.10s.
as you can see in the logs textInputProps2.getNode(1).props.value ====> show me the value as expected.
So far so good
Now passing everything to a reducer, with the redux structure, we will see the text input as follows
<TextInput value={this.props.user.email} style={styles.textInputs} placeholder={'Email'} autoCapitalize={'none'} onChangeText={(text) => {
console.log('Here change email text!!! ==> ', text);
this.props.email_typing(text);
}} />
Connected logic
const mapStateToProps = (state) => {
return {
user: state.user
}
}
const mapDispatchToProps = (dispatch) => {
return {
email_typing: (text) => dispatch(UserReducer.email_typing(text)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen)
My UserReducer File
import { createReducer, createActions } from 'reduxsauce'
import Immutable from 'seamless-immutable'
/* ------------- Types and Action Creators ------------- */
const { Types, Creators } = createActions({
email_typing: ['email'],
})
export const LoginTypes = Types
export default Creators
/* ------------- Initial State ------------- */
export const INITIAL_STATE = Immutable({
email: ''
})
/* ------------- Reducers ------------- */
// state.merge undefined error: https://github.com/infinitered/ignite/pull/20#issuecomment-202550408. Fixed including in Inmutable
export const emailTyping = (state, { email }) => {
console.log('Email Typing changes !!! in original reducer')
return Immutable(state).merge({ email })
}
/* ------------- Hookup Reducers To Types ------------- */
export const reducer = createReducer(INITIAL_STATE, {
[Types.EMAIL_TYPING]: emailTyping,
})
Given this change, the idea is that the initialState within the Test File changes to INITIAL_STATE imported value.
Something like:
const mockStore = configureStore([]);
let store = mockStore(INITIAL_STATE);
but, when i run the test again. Show me the next error:
● >>>>> LoginScreen component renders correctly
TypeError: Cannot read property 'email' of undefined
even if I keep the initialState instead of the INITIAL_STATE, I do not get the above error, but I can not get the text input to take the change.
Running Test Logs
yarn test v0.24.6
$ jest
PASS Tests/Containers/loginScreenTest.js
✓ >>>>> LoginScreen component renders correctly (345ms)
✓ >>>>> Login button Press (24ms)
console.log Tests/Containers/loginScreenTest.js:58
textInputProps.getNode(1).props.value BEFORE ====> mockState email
console.log App/Containers/LoginScreen.js:120
Here change email text!!! ==> My new value
console.log Tests/Containers/loginScreenTest.js:61
textInputProps2.getNode(1).props.value====> mockState email => **(!! HERE !!!, THE VALUE IS BEING THE PREVIOUS ONE AND IGNOR THE CHANGE)**
console.log Tests/Containers/loginScreenTest.js:79
state ===> { user:
{ email: 'mockState email',
password: '',
requesting: 0,
userData: null,
loginFinish: false,
errorMessage: null } }
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 1 passed, 1 total
Time: 2.904s
Ran all test suites.
✨ Done in 3.68s.
Check textInputProps2.getNode(1).props.value====> log to check that this is not useful.
I think that the const initialState declared inside test file It is not being affected by the changes made in the actual reducer when this.props.email_typing(text) action is called;
I have not found the way to connect the actions with the states in the reducer and to be able to load them inside JEST.
I know it's a bit long and I appreciate your time reading it.
I tried to leave it the best explained and as much information as possible.
Thank you very much and I look forward to any response.
I guess you want to do some integration test here. It is possible to achieve what you are trying like that :
import { createStore, combineReducers } from 'redux';
import { reducer } from '.../UserReducer';
// create a real store with the needed reducer(s)
const store = createStore(combineReducers({ user: reducer }));
const wrapper = shallow(
<LoginScreen/>,
{ context: { store } },
);
// ...
test('>>>>> Login button Press', () => {
let render = wrapper.dive();
const textInputProps = render.find('TextInput');
console.log(`value BEFORE ====>`, textInputProps.getNodes()[0].props.value);
textInputProps.first().simulate('changeText', 'My new value');
// Force the component to update after changing state
render = wrapper.update().dive();
const textInputProps2 = render.find('TextInput');
console.log(`value AFTER ====>`, textInputProps2.getNodes()[0].props.value);
const state = store.getState();
console.log('state ===> ', state);
});
I tried with a minimal implementation, here is the console result:
console.log src/Test.test.js:27
value BEFORE ====>
console.log src/Test.test.js:35
value AFTER ====> My new value
console.log src/Test.test.js:38
state ===> { user: { email: 'My new value' } }
Related
I have a component in my Vue 3 app which displays a checkbox. The checkbox can be manually checked by the user but it can also be checked/unchecked as a result of a Pinia state change. I'm pretty new to Unit Testing but I would assume that a good unit test for this component would include checking whether or not the checkbox reacts to the Pinia state correctly. However, in my Unit Test, when I change the Pinia state, the checkbox value does not change (the component itself works fine, it's only in the Unit Test that this does not work). Does anyone know what I am doing wrong?
As well as calling the store action to update the state I have also tried store.$patch and that doesn't work either.
This is my component:
<template>
<div class="field-checkbox">
<Checkbox role="checkbox" :aria-label="displayName" #change="checkGroupMember()" v-model="checked" :binary="true" />
<label>{{displayName}}</label>
</div>
</template>
<script setup lang="ts">
import { useContactBookStore } from "#/stores/contactBookStore";
import { ref, watch } from "vue";
import { storeToRefs } from "pinia";
const store = useContactBookStore();
const props = defineProps({
groupMember: { type:Object, required: true }
});
const checked = ref(false);
const { getCheckedGroupMembers } = storeToRefs(store)
const displayName = ref(props.groupMember.title + " " + props.groupMember.firstName + " " + props.groupMember.lastName);
// set the initial value of the checkbox
updateCheckBox();
// watch the value of getCheckedGroupMembers in the store and if it
// changes re-evaluate the value of the checkbox
watch(getCheckedGroupMembers , () => {
updateCheckBox();
},{ deep: true })
// when the checkbox is checked/unchecked, run the checkUser method
// in the store
function checkGroupMember() {
const groupMember = {
id:props.groupMember.id,
title:props.groupMember.title,
firstName:props.groupMember.firstName,
lastName:props.groupMember.lastName
}
store.checkGroupMember(groupMember,checked.value);
}
// the checkbox is checked if the user is among the checked users
// in the store
function updateCheckBox() {
const groupMember = {
id: props.groupMember.id,
title: props.groupMember.title,
firstName: props.groupMember.firstName,
lastName: props.groupMember.lastName
}
const exists = getCheckedGroupMembers.value.find((member) => member.id === groupMember.id)
checked.value = !!exists;
}
</script>
and this is my Unit Test:
import { render, screen } from "#testing-library/vue";
import GroupMember from "#/components/ContactBook/GroupMember.vue";
import { describe, it, vi, expect, beforeEach, afterEach } from "vitest";
import { createTestingPinia } from "#pinia/testing";
import PrimeVue from "primevue/config";
import { createPinia, setActivePinia } from "pinia";
import Checkbox from 'primevue/checkbox';
import { useContactBookStore } from "#/stores/contactBookStore";
describe("GroupMember", () => {
const mockUser:GroupMember = {id:"TT001",title:"Mr",firstName:"Ted",lastName:"Tester"}
let mockProps = {groupMember:mockUser};
render(GroupMember, {
props: mockProps,
global: {
components: {Checkbox},
plugins: [PrimeVue,
createTestingPinia({
initialState: {contactBook:{checkedGroupMembers:[mockUser]}},
stubActions: false,
createSpy: vi.fn,
fakeApp:true
}),
],
},
});
setActivePinia(createPinia());
it("Displays the user name in the correct format", async() => {
const displayName = mockProps.groupMember.title + " " + mockProps.groupMember.firstName + " " + mockProps.groupMember.lastName;
screen.getByText(displayName)
});
it("Shows the checkbox initially checked", async() => {
let checkbox:any;
const displayName = mockProps.groupMember.title + " " + mockProps.groupMember.firstName + " " + mockProps.groupMember.lastName;
checkbox = screen.getAllByRole("checkbox", { name: displayName })[1]
expect(checkbox.checked).toBe(true)
});
it("Should display the checkbox as unchecked when the store is updated", async() => {
let checkbox:any;
const displayName = mockProps.groupMember.title + " " + mockProps.groupMember.firstName + " " + mockProps.groupMember.lastName;
checkbox = screen.getAllByRole("checkbox", { name: displayName })[1]
const store = useContactBookStore();
await store.checkGroupMember(mockUser,false);
//await store.$patch({checkedGroupMembers:[]}) // this didn't work either
expect(checkbox.checked).toBe(false)
});
});
this is the error I get when the test runs:
54| expect(checkbox.checked).toBe(false)
| ^
55| });
56| });
- Expected "false"
+ Received "true"
When you render or mount a component in a unit test, there is no event loop running, so while you can change the state in the Pinia store, the component does not react to the alteration until "later": import { flushPromises } from '#vue/test-utils'; await flushPromises(), await component.vm.$nextTick or import { nextTick } from 'vue'; await nextTick().
See https://v1.test-utils.vuejs.org/guides/testing-async-components.html where it is described that you can await triggers like clicking on the checkbox, but if the change happens out of band, you don't necessarily have enough awaiting going on.
flushPromises is documented here
Test case works in Postman, but not from Visual Studio Code or Jest command line.
Request Body in Postman that works and returns all 4 errors:
{ "Item": {} }
This is missing the FileName and Item.Data fields.
API.dto.ts
import { Type } from 'class-transformer';
import { IsNumberString, IsString, MinLength, ValidateNested } from 'class-validator';
// Expected Payload
// {
// FileName: 'abc',
// Item: {
// Data: '123'
// }
// }
export class ItemDataDTO {
#IsNumberString() #MinLength(2) public readonly Data: string;
}
/**
* This class is the Data Object for the API route
*/
export class ApiDTO {
#IsString() #MinLength(1) public readonly FileName: string;
#ValidateNested()
#Type(() => ItemDataDTO)
Item: ItemDataDTO;
}
api.controller.ts
import { Body, Controller, Get, Options, Put, Request, Response } from '#nestjs/common';
import { ApiDTO } from './api.dto';
#Controller('api')
export class ApiController {
constructor() { }
#Put('donotuse')
public DoNotUse(#Body() APIBody: ApiDTO) {
return 'OK';
}
}
API.dto.spec.ts
import { ArgumentMetadata, ValidationPipe } from '#nestjs/common';
import { ApiDTO } from './api.dto';
describe('ApiDto', () => {
it('should be defined', () => {
expect(new ApiDTO()).toBeDefined();
});
it('should validate the ApiDTO definition', async () => {
const target: ValidationPipe = new ValidationPipe({
transform: true,
whitelist: true,
});
const metadata: ArgumentMetadata = {
type: 'body',
metatype: ApiDTO,
data: '{ "Item": {} }',
};
const Expected: string[] = [
'FileName must be longer than or equal to 1 characters',
'FileName must be a string',
'Item.Data must be longer than or equal to 2 characters',
'Item.Data must be a number string',
];
await target.transform(<ApiDTO>{}, metadata).catch((err) => {
expect(err.getResponse().message).toEqual(Expected);
});
});
});
The expect fails.
await target.transform(<ApiDTO>{}, metadata).catch((err) => {
expect(err.getResponse().message).toEqual(Expected);
});
The 2 FileName errors are returned, but no the Item.Data fields. Setting data: '', to be data: '{ "Item": {} }' also fails the same way.
Actual expectation failure:
expect(received).toEqual(expected) // deep equality
- Expected - 2
+ Received + 0
Array [
"FileName must be longer than or equal to 1 characters",
"FileName must be a string",
- "Item.Data must be longer than or equal to 2 characters",
- "Item.Data must be a number string",
]
This is indicating that the FileName validation is there, those 2 lines are returned, but the Item.Data errors, are not coming back, and are 'extra' in my test case results.
However, calling this via Postman, PUT /api/donotuse with the request body:
{ "Item": {} }
returns all 4 of the errors. The HTTP Status code is also a 400 Bad Request, as NestJS would normally return on its own. I am not sure what is wrong in my test case to get the errors to all be returned.
EDIT
I have also then tried to do this via E2E testing as the answer suggested, but I still receive the same missing errors.
describe('ApiDto - E2E', () => {
let app: INestApplication;
afterAll(async () => {
await app.close();
});
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ transform: true, errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY }));
await app.init();
});
it('should validate the ApiDTO definition', async () => {
const APIRequestDTO: unknown = { FileName: null, Item: {} };
const ResponseData$ = await request(app.getHttpServer())
.put('/api/donotuse')
.set('Accept', 'application/json')
.send(APIRequestDTO as ApiDTO);
const Expected: string[] = [
'FileName must be longer than or equal to 1 characters',
'FileName must be a string',
'Item.Data must be longer than or equal to 2 characters',
'Item.Data must be a number string',
];
expect(ResponseData$.status).toBe( HttpStatus.UNPROCESSABLE_ENTITY);
expect(ResponseData$.body.message).toBe(Expected);
});
});
This still does not provide all the errors, that are properly returned from the Postman call. I am not sure what is happening during testing that the sub type is not processed. Calling this via Postman, same body, same headers, etc., does return the proper errors:
"message": [
"FileName must be longer than or equal to 1 characters",
"FileName must be a string",
"Item.Data must be longer than or equal to 2 characters",
"Item.Data must be a number string"
],
I know it is going into the ValidationPipe as well, as my custom error code, 422 Unprocessable Entity is returned, indicating this is the validation that is failing. This same error is returned in both my unit test and the E2E test, but not the second set of errors about Item.Data.
I assume that in your app you're registering the ValidationPipe globally, eg:
app.useGlobalPipes(new ValidationPipe());
Due to the location of where Global Pipes are registered, they will work when you execute an actual request against your backend but will not be picked up in tests. This is why you're seeing it working through Postman, but not through Jest.
If you want the Validation pipe to be used in your tests you will need to manually set it up like so:
// Probably in your beforeEach where you're setting up the test module
const app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe());
Duplicate of How to apply Global Pipes during e2e tests
I have written test cases for signin API using jest. After completing all five test of a test suit jest give me following error in log.
Can any body tell Why it is So and how to fix it?
CODE:(signup.test.ts)
import request from 'supertest';
import { TYPES } from '../src/inversify.types'
import { Application } from '../src/app/Application'
import { container } from '../src/inversify.config'
import dotenv from 'dotenv'
import { RESPONSE_CODE } from '../src/utils/enums/ResponseCode'
import { RESPONSE_MESSAGES } from '../src/utils/enums/ResponseMessages'
import { UserSchema } from '../src/components/user/User';
// import jwt from 'jsonwebtoken';
var application: Application
describe("POST / - SIGNUP endpoint", () => {
// var testusers: any;
//This hook is executed before running all test cases, It will make application instance, make it to listen
// on it on port 3000 and add test document in DB
beforeAll(async () => {
// Make enviroment variables available throughout the application
dotenv.config();
// Getting application instance using iversify container
application = container.get<Application>(TYPES.Application);
// Initialize frontside of application
await application.bootstrap();
// Starting Application server on given port
await application.listen(3000);
});
afterAll(
//This hook is executed after running all test cases and delete test document in database
async () =>{
const res = await UserSchema.deleteMany({ Name: { $in: [ "Test User", "Test" ] } });
// `0` if no docs matched the filter, number of docs deleted otherwise
console.log('---------------------->>>>>>>>>>>>>>>>>>>', (res as any).deletedCount);
}
)
it("Signup for user that don\'t exists", async () => {
const response = await request(application.getServer()).post('/user/signup')
.send({
"Email": JSON.parse(process.env.TEST_USER).Email,
"Name": "Test User",
"Password": process.env.TEST_ACCOUNTS_PASSWORD
})
expect(response.status).toBe(RESPONSE_CODE.CREATED);
expect(JSON.parse(response.text)).toEqual(expect.objectContaining({
Message: RESPONSE_MESSAGES.ADDED_SUCESSFULLY,
Data: expect.objectContaining({
Name: 'Test User',
Country: '',
PhoneNumber: '',
// Password: '$2b$10$nIHLW/SA73XLHoIcND27iuODFAArOvpch6FL/eikKT78qbShAl6ry',
Dob: '',
Role: 'MEMBER',
IsEmailVerified: false,
IsBlocked: 'ACTIVE',
IsTokenSent: false,
twoFAStatus: false,
// _id: '5c812e2715e0711b98260fee',
Email: JSON.parse(process.env.TEST_USER).Email
})
})
);
console.log('*** Signup for user that don\'t exists *** response', response.text, 'response status', response.status);
});
it("Signup for user that exists", async () => {
const response = await request(application.getServer()).post('/user/signup')
.send({
"Email": JSON.parse(process.env.TEST_USER).Email,
"Name": "Test User",
"Password": process.env.TEST_ACCOUNTS_PASSWORD
})
expect(response.status).toBe(RESPONSE_CODE.CONFLICT);
expect(JSON.parse(response.text)).toEqual({
Message: RESPONSE_MESSAGES.ALREADY_EXISTS
})
console.log('*** Signup for user that don\'t exists *** response', response.text, 'response status', response.status);
});
});
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't
stopped in your tests. Consider running Jest with
--detectOpenHandles to troubleshoot this issue.
Cannot log after tests are done. Did you forget to wait for something
async in your test?
Attempted to log "{ accepted: [ 'unverifiedtestuser#abc.com' ],
rejected: [],
envelopeTime: 621,
messageTime: 867,
messageSize: 906,
response: '250 2.0.0 OK 1551945300 f6sm5442066wrt.87 - gsmtp',
envelope:
{ from: 'abc#gmail.com',
to: [ 'unverifiedtestuser#abc.com' ] },
messageId: '<45468449-b5c8-0d86-9404-d55bb5f4g6a3#gmail.com>' }".
at CustomConsole.log (node_modules/jest-util/build/CustomConsole.js:156:10)
at src/email/MailHandler.ts:2599:17
at transporter.send.args (node_modules/nodemailer/lib/mailer/index.js:226:21)
at connection.send (node_modules/nodemailer/lib/smtp-transport/index.js:247:32)
at callback (node_modules/nodemailer/lib/smtp-connection/index.js:435:13)
at stream._createSendStream (node_modules/nodemailer/lib/smtp-connection/index.js:458:24)
at SMTPConnection._actionSMTPStream (node_modules/nodemailer/lib/smtp-connection/index.js:1481:20)
at SMTPConnection._responseActions.push.str (node_modules/nodemailer/lib/smtp-connection/index.js:968:22)
at SMTPConnection._processResponse (node_modules/nodemailer/lib/smtp-connection/index.js:764:20)
at SMTPConnection._onData (node_modules/nodemailer/lib/smtp-connection/index.js:570:14)
I was using the react-native default test case (see below) when Cannot log after tests are done happened.
it('renders correctly', () => {
renderer.create(<App />);
});
Apparently, the problem was that the test ended but logging was still needed. So I tried to make the callback in the test case async, hoping that the test won't terminate immediately:
it('renders correctly', async () => {
renderer.create(<App />);
});
And it worked. However, I have very little clue what the inner working is.
If you are using async/await type in your code, then this error can occur when you are calling async function without await keyword.
In my case, I have defined a function like this below,
async getStatistics(headers) {
....
....
return response;
}
But I have called this method like getStatistics(headers) instead of await getStatistics(headers).
When I included await, it worked fine and the issue resolved.
In my case while using nodejs + jest + supertest the problem was that when I import app from "./app" to my test file to do some stuff with supertest (request(app)), I actually import with app.listen() , because when I'm exporting app, export takes in account app.listen() too, but we don't need app.listen() in tests and it throws an error
"Cannot log after tests are done.Did you forget to wait for something async in your test?"
Here is all in one file(that's the problem!)
const app = express();
app.use(express.json());
// ROUTES
app.get("/api", (req, res) => {
res.json({ message: "Welcome to Blog API!" });
});
app.use("/api/users", usersRoutes);
app.use("/api/blogs", blogsRouter);
// The server will start only if the connection to database is established
mongoose
.connect(process.env.MONGO_URI!)
.then(() => {
console.log("MongoDB est connecté");
const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`The server is running on port: ${port}`));
})
.catch(err => {
console.log(err);
});
export default app;
To solve this issue I created 2 separate folders:
// 1) app.ts
Where I put all stuff for my const app = express(), routes etc and export app
dotenv.config();
const app = express();
app.use(express.json());
// ROUTES
app.get("/api", (req, res) => {
res.json({ message: "Welcome to Blog API!" });
});
app.use("/api/users", usersRoutes);
app.use("/api/blogs", blogsRouter);
export default app;
// 2) index.ts
Where I put app.listen() and mongoose.connection() and import app
*import mongoose from "mongoose";
import app from "./app";
// The server will start only if the connection to database is established
mongoose
.connect(process.env.MONGO_URI!)
.then(() => {
console.log("MongoDB est connecté");
const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`The server is running on port: ${port}`));
})
.catch(err => {
console.log(err);
});*
For me I needed to add an await before the expect() call also to stop this error (and an async before the test() callback function).
Also caused and fixed Jest not detecting coverage on the lines in the code throwing the error!
test("expect error to be thrown for incorrect request", async () => {
await expect(
// ^ added this
async () => await getData("i-made-this-up")
).rejects.toThrow(
"[API] Not recognised: i-made-this-up"
);
});
getData() returns an Axios call and in this case an error is caught by catch and re-thrown.
const getData = async (id) => {
return await axios
.get(`https://api.com/some/path?id=${id}`)
.then((response) => response.data)
.catch((error) => {
if (error?.response?.data?.message) {
console.error(error) // Triggered the error
throw new Error("[API] " + error.response.data.message);
}
throw error;
});
};
This happened to me because I had an infinite loop while (true). In my case, I was able to add a method for setting the value of the loop based on user input, rather than defaulting to true.
In my case, the error was caused by asynchronous Redis connection still online. Just added afterall method to quit Redis and could see the log again.
Working on Typescript 4.4.2:
test("My Test", done => {
let redisUtil: RedisUtil = new RedisUtil();
let redisClient: Redis = redisUtil.redis_client();
done();
});
afterAll(() => {
redisClient.quit();
});
I solved it with the env variables:
if (process.env.NODE_ENV !== 'test') {
db.init().then(() => {
app.listen(PORT, () => {
console.log('API lista por el puerto ', PORT)
})
}).catch((err) => {
console.error(err)
process.exit(1)
})
} else {
module.export = app
}
I faced same warnings. However the fix is bit weird:
The jest unit test script import a function (which is not export from src/). After I added the export to the function to be tested. The error disappears.
I had a similar issue:
Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "Warning: You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. ".
It was due to a missing static keyword. This code caused the issue:
class MyComponent extends React.Component<Props, State> {
propTypes = {
onDestroy: PropTypes.func,
}
}
It should have been:
class MyComponent extends React.Component<Props, State> {
static propTypes = {
onDestroy: PropTypes.func,
}
}
I have a Vue component, which is using a mapped action from a vuex store, which returns a promise. When the component calls the mapped action, and the mapped action is resolved, I am calling another vue method vm.$router.push(). I want to assert that the push method gets called. Here is my component, test, and some helper methods I created to sub out the component with vuex and vue-router.
Here is my .vue component with some console.logs to track what's going on.
<template>
<div>
<button #click="promise" class="click-promise">Promise</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions(['promiseAction']),
promise(){
const vm = this
vm.$router.push('/some/route')
console.log('before promiseAction')
console.log(vm.promiseAction())
return vm.promiseAction().then(function (response) {
console.log('inside promiseAction')
vm.$router.push('/some/other/route')
})
}
}
}
</script>
Here is my test. I'm using Mocha, Karma, Chai, and jquery-chia
import Testing from '#/components/Testing'
describe('Testing.vue', () => {
const mount = componentHelper(Testing)
it.only('assert something in a promise returned from an action', () => {
const promise = new Promise(resolve => resolve('success'))
const promiseAction = stubAction('promiseAction').returns(promise)
const vm = mount()
const routerPush = sinon.spy(vm.$router, 'push')
$('.click-promise').click()
expect(promiseAction).to.have.been.called
expect(routerPush).to.have.been.calledWith('/some/route') //this passes
return vm.$nextTick(() => {
console.log('inside nextTick')
expect(routerPush).to.have.been.calledWith('/some/other/routes') //this never happens
})
})
})
And here is my helpers file. I'm not sure if this is 100% neccisary, but I wanted to include everything in this post
import Vue from 'vue'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
Vue.use(Vuex)
Vue.use(VueRouter)
let div
beforeEach(() => {
// create a dom element for the component to mount to
div = document.createElement('div')
document.body.appendChild(div)
})
afterEach(() => {
// clean up the document nodes after each test
Array.prototype.forEach.call(document.querySelectorAll('body *:not([type="text/javascript"])'), node => {
node.parentNode.removeChild(node)
})
})
// stub out a store config object
const storeConfig = {
actions: {}
}
/**
* Set up a function that will attach the mock store to the component
* and mount the component to the test div element
*
* Use like this:
* const mount = componentHelper(YourComponent)
* do some setup
* call mount() to instantiate the mocked component
*
* #param component
* #returns {function()}
*/
window.componentHelper = function (component) {
const router = new VueRouter({})
return () => {
// 1. attaches the stubbed store to the component
component.store = new Vuex.Store(storeConfig)
component.router = router
// 2. mounts the component to the dom element
// 3. returns the vue instance
return new Vue(component).$mount(div)
}
}
/**
* Creates an action to be added to the fake store
* returns a sinon stub which can be asserted against
* #param actionName
*/
window.stubAction = (actionName) => {
// 1. create a stub
const stub = sinon.stub()
// 2. create the action function that will be placed in the store and add it to the store
storeConfig.actions[actionName] = function (context, ...args) {
// 3. when this action is called it will call the stub
// return the stub so you can assert against the stubbed return value
// example: stubAction('fakeAction').returns('xyz')
return stub(...args)
}
// 4. return the stub that was placed in the return of the action for assertions
return stub
}
When I run this test this is what I get.
LOG LOG: 'before promiseAction'
LOG LOG: Promise{_c: [], _a: undefined, _s: 1, _d: true, _v: 'success', _h: 0, _n: true}
Testing.vue
✓ assert something in a promise returned from an action
PhantomJS 2.1.1 (Linux 0.0.0): Executed 1 of 4 SUCCESS (0.045 secs / 0.018 secs)
TOTAL: 1 SUCCESS
=============================== Coverage summary ===============================
Statements : 31.58% ( 6/19 )
Branches : 100% ( 0/0 )
Functions : 0% ( 0/2 )
Lines : 31.58% ( 6/19 )
================================================================================
LOG LOG: 'inside nextTick'
ERROR LOG: '[Vue warn]: Error in nextTick: "AssertionError: expected push to have been called with arguments /some/other/routes
/some/route /some/other/routes "
(found in <Root>)'
ERROR LOG: AssertionError{message: 'expected push to have been called with arguments /some/other/routes
/some/route /some/other/routes ', showDiff: false, actual: push, expected: undefined, stack: undefined, line: 210, sourceURL: 'http://localhost:9877/absolute/home/doug/Code/projects/vue-testing-sandbox/node_modules/chai/chai.js?ab7cf506d9d77c111c878b1e10b7f25348630760'}
LOG LOG: 'inside promiseAction'
As you can see the test passes, but the code inside of the promise does not run until after the test is done. I'm talking about this section
return vm.promiseAction().then(function (response) {
console.log('inside promiseAction')
vm.$router.push('/some/other/route')
})
I also logged out the promise function console.log(vm.promiseAction()) and you can see it is what you would expect.
How can I get the test to wait for the promise? I thought nextTick might be the answer, but it doesn't seem to be working.
Thanks for any help.
There isn't a really good way to do what you want via clicking the button. Moreover, I'm not sure it's even really worth testing it through clicking the button. If Vue's event handlers aren't working correctly you have bigger problems.
Instead I would suggest you just call the promise method and execute your tests in the success callback of the promise returned from that method.
//execute the handler
const test = vm.promise()
// test that the pre-async actions occured
expect(promiseAction).to.have.been.called
expect(routerPush).to.have.been.calledWith('/some/route')
// test the post-async action occurred
return test.then(() => {
expect(routerPush).to.have.been.calledWith('/some/other/routes')
})
[This is a Vue app, using Vuex, created with vue-cli, using mocha, chai, karma, sinon]
I'm trying to create tests for my vuex state and I DON'T want to use a mock -- one of my big goals for these tests is to also test the API that data is coming from.
I am trying to follow the docs for chai-as-promised.
This is a simplification of the vuex action I'm trying to test:
const actions = {
login: (context, payload) => {
context.commit('setFlashMessage', "");
axios.get("https://first-api-call")
.then((response) => {
axios.post("https://second-api-call")
.then((response) => {
router.push({ name: "Home"});
context.commit('setFlashMessage', "Logged in successfully");
context.commit('setLogin', response.data);
});
},
Notice that the login action has two promises and doesn't return anything. The login action does two things: it sets some state and it changes the route.
The example that I've seen that using chai-as-promised expects that the promise is returned. That is:
var result = systemUnderTest();
return expect(result).to.eventually.equal(blah);
But in my case, login() doesn't return anything, and I'm not sure what I would return if it did.
This is what I have so far:
import store from '#/src/store/store'
describe('login', () => {
it('bad input', () => {
store.login({ username: "abcd", password: ""});
// What is the test I should use?
}
}
I would return the login response message and make two tests. One to make sure that invalid credentials return a failure message and one to make sure that valid credentials login successfully
My co-worker and I came up with the solution:
The vuex action needs to return the promise, and they can be chained together:
login: (context, payload) => {
context.commit('setFlashMessage', "");
return axios.get("https://first-api-call")
.then((response) => {
return axios.post("https://second-api-call")
})
.then((response) => {
// etc...
router.push({ name: "Home"});
context.commit('setFlashMessage', "Logged in successfully");
context.commit('setLogin', response.data);
return {status: "success"};
});
},
Then we didn't need chai-as-promised because the test looks like this:
it('bad password', () => {
const result = store.dispatch("login", { username: userName, password: password + "bad" });
return result.then((response) => {
expect(response).to.deep.equal({ status: "failed"});
store.getters.getFlashMessage.should.equal("Error logging in");
});
});