I have the following spec:
context('Contact update', () => {
it.only('Can update contact', () => {
const address = 'new address 123'
const cardId = 'c2card-38AF429999C93B5DC844D86381734E62'
cy.viewport('macbook-15')
cy.authVisit('/contact/list')
cy.getByTestId('open-doc-Aaron Dolney-0').click()
cy.get('[name="physicaladdress"').type(`{selectall}{backspace}${address}`)
cy.getByTestId(cardId, 'save-button').click()
cy.getByTestId(cardId, 'loading')
cy.get('[name="physicaladdress"').should('have.value', address)
})
})
getByTestId is a command I wrote to reduce some boilerplate:
Cypress.Commands.add('getByTestId', (...ids) => {
const id = ids.map(id => `[data-testid="${id}"]`).join(' ')
return cy.get(id)
})
When I run this with anything other than cypress open, it fails on getting the loading indicator. I'm thinking my test endpoint is responding too fast and toggling the loading indicator too quickly.
Is there a better, more consistent, way to verify the loading indicator shows?
I was struggling with this as well since I wanted to make sure the loading indicator was showing so I had to get a bit crafty. I found that cy.route() has an onRequest option and that ended up being just what I needed.
https://docs.cypress.io/api/commands/route.html#Options
cy.server();
// Verify that the loading indicator is shown before the request finishes
cy.route({
url: url_of_request_that_triggers_loading,
onRequest: () => {
cy.getByTestId(cardId, 'loading');
},
});
With this implementation we run the loading expectation before the request finishes and before the loading indicator disappears.
I found quite a reliable although bit complicated solution to that. I uses jQuery Deferred (bundled with cypress) along with internal cypress state (via aliases).
For extensive testing of intermediate states it works great.
/**
* Workaround for delegating {#link Cypress.Chainable['intercept']} - internal Method type is
* not exported and thus delegating it is difficult. It's also difficult to extract it from
* the function type because of overloads. If any more methods are needed, feel free
* to add them here
*/
type Method = 'GET' | 'POST' | 'PATCH' | 'DELETE';
// extend this type if you need more flexibility
export interface ResponseStub {
status: number;
response: unknown;
}
/**
* Deferred needs to be wrapped in an object because wrap() acts differently on promise-like args
*/
type InertDeferred = { deferred: Deferred<Partial<ResponseStub> | undefined> };
const deferredAlias = (aliasId: string): string => `${aliasId}Deferred`;
const deferredAliasSelector = (aliasId: string): string => `#${deferredAlias(aliasId)}`;
export const deferredRequestCommand = (
method: Method,
url: string,
aliasId: string,
fixture: string,
): void => {
const deferred: InertDeferred['deferred'] = Cypress.$.Deferred();
cy.wrap({ deferred }).as(deferredAlias(aliasId));
cy.fixture(fixture).then((data) => {
cy.intercept(method, url, (req) => {
deferred
.then((stub) => {
req.reply(stub?.status || 200, stub?.response || data);
});
return deferred.promise();
});
});
};
export const resolveDeferredRequestCommand = (
aliasId: string,
stub: Partial<ResponseStub> = undefined,
): void => {
cy.get<InertDeferred>(deferredAliasSelector(aliasId))
.invoke('deferred.resolve', stub);
};
Related
after running mutation using the graphql, if I quickly goback to Previous page,
occur error : Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and
asynchronous tasks in %s.%s, a useEffect cleanup function,
I think it's because I quickly go to another page during the mutation.
If this is not the case, there is no error.
(Even if an error occurs, update succeeds. but I'm worried about errors)
Even if move to another page during mutating, I want to proceed with the update as it is
How can I proceed with the update?
if If there is no way, is there method that How to create a delay during mutating
im so sorry. my english is not good.
const CalendarTodo = ({
month,
day,
data,`enter code here`
isImportWhether,
setIsImportWhether
}) => {
const [value, setValue] = useState("");
const monthDay = `${month + 1}월 ${day}일`;
const [createToDoMutation] = useMutation(CREATE_TODO, {
variables: {
toDoId:
data &&
data.toDos &&
data.toDos.filter(object => object.monthDay === monthDay)[0] &&
data.toDos.filter(object => object.monthDay === monthDay)[0].id,
monthDay: monthDay,
dayToDo: value,
importEvent: isImportWhether
},
update: (proxy, { data: { createToDo } }) => {
const data = proxy.readQuery({ query: SEE_TODO_OF_ME });
data &&
data.toDos &&
data.toDos.filter(object => object.monthDay === monthDay)[0] &&
data.toDos
.filter(object => object.monthDay === monthDay)[0]
.dayToDo.push(createToDo);
proxy.writeQuery({ query: SEE_TODO_OF_ME, data });
},
optimisticResponse: {
createToDo: {
__typename: "DayToDo",
id: Math.random().toString(),
toDoList: value,
importEvent: isImportWhether
}
}
});
return (
<>
);
};
export default CalendarTodo;
As you already guessed the reason is the asynchronous request that keeps on running even after un-mounting the component due to navigating away from it.
There are many ways to solve this. One is to add a check whether or not the component you are calling the async request from is still mounted and only update its state if so, e.g.:
useEffect(() => {
let isMounted = true;
apollo.mutate({query, variables, update: {
if(isMounted) {
// update state or something
}
})
return () => {
isMounted = false;
};
}, []);
This way however the data might be lost. If you want to make sure that you receive and store the return value you should add the request to a higher level component or context hat will not be unmounted on navigation. This way you can trigger the async call but dont have to worry about navigating away.
I have a problem with updating props in my test after some code refactor. I use custom render and mock axios request but my component doesn't rerender (?). In my component in async ComponentDidMount() I do POST request. When I do manual test in browser everything works fine.
I receive exception produced by getByText():
Unable to find an element with the text: /Tasty Metal Keyboard/i. This
could be because the text is broken up by multiple elements. In this
case, you can provide a function for your text matcher to make your
matcher more flexible.
/** import React, mockAxios etc. */
const middleware = applyMiddleware(thunk);
const inputRootPath = document.createElement('input');
inputRootPath.id = 'rootPath';
inputRootPath.hidden = true;
inputRootPath.value = 'http://localhost/';
/**
*
* #param {*} ui komponent
* #param {*} param { initialState, store }
*/
export function renderWithRedux(
ui,
{ initialState, store = createStore(rootReducer, initialState, compose(middleware)) } = {},
) {
return {
...render(
<Provider store={store}>
{ui}
</Provider>,
{ container: document.body.appendChild(inputRootPath) }
),
store,
};
}
test('should render annex list', async () => {
const agBuilder = () => {
return {
ID: faker.random.number(),
NM: faker.commerce.productName(),
};
};
const agreements = [agBuilder(), agBuilder(), agBuilder(), agBuilder()];
mockAxios.post.mockResolvedValueOnce({ data: { ANLST: agreements } });
const { getByText, } = await renderWithRedux(<ConnectedAgreements />);
const optionRE = new RegExp(`${agreements[0].NM}`, 'i');
expect(getByText(optionRE)).toBeInTheDocument();
mockAxios.post.mockClear();
});
mocks/axios.js
export default {
get: jest.fn().mockResolvedValue({ data: {} }),
post: jest.fn().mockResolvedValue({ data: {} }),
};
I found solution. It turns out that after some code refactor I have another reducer which takes dispatch action invoked in CDM. It destructure axios response so my test code should have:
mockAxios.post.mockResolvedValueOnce({ data: { ANLST: agreements, CLS: {}, EXLDT: {} } });
Missing CLS and EXLDT properties casue test fail. Jest however doesn't print error that something is missing or undefined ¯_(ツ)_/¯ . Exception produced by getByText() was misleading.
I am learning react redux, I am using firebase for storing data.
I installed thunk middleware. Everything works, I just don't understand why.
As I understand it, the const expense is an object which is in another function's scope. How can addExpense gets access to it?
export const addExpense = (expense) => ({
type: 'ADD_EXPENSE',
expense
});
export const startAddExpense = (expenseData = {}) => {
return (dispatch) => {
const {
description = '',
note = '',
amount = 0,
createdAt = 0
} = expenseData;
const expense = { description, note, amount, createdAt };
database.ref('expenses').push(expense).then((ref) => {
dispatch(addExpense({
id: ref.key,
...expense
}));
});
};
};
startAddExpense is passing the const expense object to your addExpense function, along with an id field. It just so happens that the argument to addExpense is also called expense, which is where you might be getting confused.
Hope that clears it up.
Not sure is there any way to set default request headers in rxjs like we do with axios js as-
axios.defaults.headers.common['Authorization'] = 'c7b9392955ce63b38cf0901b7e523efbf7613001526117c79376122b7be2a9519d49c5ff5de1e217db93beae2f2033e9';
Here is my epic code where i want to set request headers -
export default function epicFetchProducts(action$, store) {
return action$.ofType(FETCH_PRODUCTS_REQUEST)
.mergeMap(action =>
ajax.get(`http://localhost/products?${action.q}`)
.map(response => doFetchProductsFulfilled(response))
);
}
Please help.
It's not possible to set default headers for all ajax requests using RxJS's ajax utilities.
You can however provide headers in each call, or create your own simple wrapper that provides them by default.
utils/ajax.js
const defaultHeaders = {
Authorization: 'c7b9392955ce63b38cf090...etc'
};
export const get = (url, headers) =>
ajax.get(url, Object.assign({}, defaultHeaders, headers));
my-example.js
import * as ajax from './utils/ajax';
// Usage is the same, but now with defaults
ajax.get(`http://localhost/products?${action.q}`;)
I'm using redux-observable but this applies to rxjs; maybe the next answer its too over-engineered, but I needed to get dinamically the headers depending of certain factors, without affecting the unit testing (something decoupled from my epics too), and without changing the sintax of ajax.get/ajax.post etc, this is what I found:
ES6 has proxies support, and after reading this and improving the solution here, I'm using a High Order Function to create a Proxy in the original rxjs/ajax object, and return the proxified object; below is my code:
Note: I'm using typescript, but you can port it to plain ES6.
AjaxUtils.ts
export interface AjaxGetHeadersFn {
(): Object;
}
// the function names we will proxy
const getHeadersPos = (ajaxMethod: string): number => {
switch (ajaxMethod) {
case 'get':
case 'getJSON':
case 'delete':
return 1;
case 'patch':
case 'post':
case 'put':
return 2;
default:
return -1;
}
};
export const ajaxProxy = (getHeadersFn: AjaxGetHeadersFn) =>
<TObject extends object>(obj: TObject): TObject => {
return new Proxy(obj, {
get(target: TObject, propKey: PropertyKey) {
const origProp = target[propKey];
const headersPos = getHeadersPos(propKey as string);
if (headersPos === -1 || typeof origProp !== 'function') {
return origProp;
}
return function (...args: Array<object>) {
args[headersPos] = { ...args[headersPos], ...getHeadersFn() };
// #ts-ignore
return origProp.apply(this, args);
};
}
});
};
You use it this way:
ConfigureAjax.ts
import { ajax as Ajax } from 'rxjs/ajax'; // you rename it
// this is the function to get the headers dynamically
// anything, a function, a service etc.
const getHeadersFn: AjaxGetHeadersFn = () => ({ 'Bearer': 'BLABLABLA' });
const ajax = ajaxProxy(getHeadersFn)(Ajax); // proxified object
export default ajax;
Anywhere in you application you import ajax from ConfigureAjax.ts and use it as normal.
If you are using redux-observable you configure epics this way (injecting ajax object as a dependency more info here):
ConfigureStore.ts
import ajax from './ConfigureAjax.ts'
const rootEpic = combineEpics(
fetchUserEpic
)({ ajax });
UserEpics.ts
// the same sintax ajax.getJSON, decoupled and
// under the covers with dynamically injected headers
const fetchUserEpic = (action$, state$, { ajax }) => action$.pipe(
ofType('FETCH_USER'),
mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe(
map(response => ({
type: 'FETCH_USER_FULFILLED',
payload: response
}))
)
);
Hope it helps people looking for the same :D
When writing a Mocha test spec against an action creator how can I be certain what a timestamp will be if it is generated within the action creator?
It doesn't have to utilize Sinon, but I tried to make use of Sinon Fake Timers to "freeze time" and just can't seem to get this pieced together wither with my limited knowledge of stubbing and mocking. If this is considered a Redux anti-pattern please point me in a better direction, but my understanding is that Redux action creators can be non-pure functions, unlike reducers.
Borrowing a little from the Redux Writing Tests Recipes here is the core of my problem as I understand it...
CommonUtils.js
import moment from 'moment';
export const getTimestamp = function () {
return moment().format();
};
TodoActions.js
import { getTimestamp } from '../../utils/CommonUtils';
export function addTodo(text) {
return {
type: 'ADD_TODO',
text,
timestamp: getTimestamp() // <-- This is the new property
};
};
TodoActions.spec.js
import expect from 'expect';
import * as actions from '../../actions/TodoActions';
import * as types from '../../constants/ActionTypes';
import { getTimestamp } from '../../utils/CommonUtils';
describe('actions', () => {
it('should create an action to add a todo', () => {
const text = 'Finish docs';
const timestamp = getTimestamp(); // <-- This will often be off by a few milliseconds
const expectedAction = {
type: types.ADD_TODO,
text,
timestamp
};
expect(actions.addTodo(text)).toEqual(expectedAction);
});
});
When testing time I have used this library successfully in the past: https://www.npmjs.com/package/timekeeper
Then in a beforeEach and afterEach you can save the time to be something specific and make your assertions then reset the time to be normal after.
let time;
beforeEach(() => {
time = new Date(1451935054510); // 1/4/16
tk.freeze(time);
});
afterEach(() => {
tk.reset();
});
Now you can make assertions on what time is being returned. Does this make sense?
I would still love to see other answers but I finally got a reasonable solution. This answer uses proxyquire to override/replace the getTimestamp() method defined in CommonUtils when used by TodoActions for the duration of the test.
No modifications to CommonUtils.js or TodoActions.js from above:
TodoActions.spec.js
import expect from 'expect';
import proxyquire from 'proxyquire';
import * as types from '../../constants/ActionTypes';
const now = '2016-01-06T15:30:00-05:00';
const commonStub = {'getTimestamp': () => now};
const actions = proxyquire('../../actions/TodoActions', {
'../../utils/CommonUtils': commonStub
});
describe('actions', () => {
it('should create an action to add a todo', () => {
const text = 'Finish docs';
const timestamp = now; // <-- Use the variable defined above
const expectedAction = {
type: types.ADD_TODO,
text,
timestamp
};
expect(actions.addTodo(text)).toEqual(expectedAction);
});
});