I have a case where I would like to use invoke the t test handler with the t.ctx context available outside of the test() function. Is this possible at all? From what I see when I run this script I do see the console spitting out all the available handler options to me, but trying to create an empty user form t.ctx.userForm = {} throws the error:
Cannot implicitly resolve the test run in the context of which the test controller action should be executed. Use test function's 't' argument instead.
import { t } from 'Testcafe';
const setFormContext = (t) => {
console.log(t);
t.ctx.userForm = {};
};
export const intializeEmptyForm = (async(t) => {
await setFormContext(t)
})(t);
I basically want to be able to have code like such, but without overcbloating the POM AccountPage object with custom functions not related to what's on the page, or relying on something like firstName to be invoked in order to make the t.ctx.userForm available.
export const AccountPage = {
enterFirstName: async (firstName) => {
let firstNameField = Selector('#firstName');
await t.typeText(firstNameField, firstName);
// t.ctx.userForm = {}; <- ideally not here as it's tied to enterFirstName
t.ctx.userForm.firstName = firstName;
},
enterLastName: async (lastName) => {
let lastNameField = Selector('#lastName');
await t.typeText(lastNameField, lastName);
t.ctx.userForm.lastName = lastName;
}
// ... some logic that maps t.ctx.userForm values to an assertion that checks all form values after clicking 'Save' are actually present.
}
import { AccountPage } from 'AccountPage';
...
test('User form successfully saves and updates correctly', async () => {
await AccountPage.enterFirstName('First');
await AccountPage.enterLastName('Last');
await AccountPage.clickSave()
})
The import {t} from 'testcafe' statement looks for a test(), beforeEach(), afterEach() or other test function in the call stack and gets the t instance from its arguments. This error occurs when an imported t is used in a function that is not called from a test or hook. This is what happens in your case, since the arrow function whose promise is exported in initializeEmptyForm is self-invoked.
As a solution, you can export a function in initializeEmptyForm (not a promise) and call it from test context.
helper.js
import { t } from 'testcafe';
export const initializeEmptyForm = async () => {
await setFormContext(t);
};
test.js
import { initializeEmptyForm } from './helper.js';
fixture 'fixture 1'
.beforeEach(async t => {
await initializeEmptyForm();
});
test('test 1', async t => {
// ...
});
Alternatively, you can export a function that takes t as an argument:
helper.js
export const initializeEmptyForm = async t => {
await setFormContext(t);
};
test.js
import { initializeEmptyForm } from './helper.js';
fixture 'fixture 1'
.beforeEach(async t => {
await initializeEmptyForm(t);
});
test('test 1', async t => {
// ...
});
My thoughts on this are when visiting a form with a click action then it may be cleaner to do so as part of the click action.
import { Selector, t } from 'testcafe';
export const AccountPage = {
clickEditForm: async () => {
let editButton = Selector('button').withText('Edit');
await t.click(editButton);
// guarantees on a click form we have setup the userForm object;
t.ctx.userForm = {};
},
enterFirstName: async (firstName) => {
let firstNameField = Selector('#firstName');
await t.typeText(firstNameField, firstName);
t.ctx.userForm.firstName = firstName;
},
enterLastName: async (lastName) => {
let lastNameField = Selector('#lastName');
await t.typeText(lastNameField, lastName);
t.ctx.userForm.lastName = lastName;
}
// map t.ctx.userForm values to assertions that checks all form values after clicking 'Save'.
verifyAccountFormDetails: async(expectFormValue = []) => {
// Grab form values
// Then map them to parameters desired or something.
}
}
This allows us to then pass the values around in a cleaner manner with the POM.
import { AccountPage } from 'AccountPage';
...
test('User form successfully saves and updates correctly', async () => {
await AccountPage.enterFirstName('First');
await AccountPage.enterLastName('Last');
await AccountPage.clickSave();
...
// After doing something like account form save then verify values
persist based on what you want to check
await AccountPage.verifyAccountFormDetails(['firstName', 'email'])
})
Related
I´m writing some tests for my app and I´m trying to mock Linking module. I'm using jest.
The Linking.canOpenURL mock it's working fine (toHaveBeenCalled is returning true), but openURL mock is never called.
function mockSuccessLinking() {
const canOpenURL = jest
.spyOn(Linking, 'canOpenURL')
.mockImplementation(() => Promise.resolve(true));
const openURL = jest
.spyOn(Linking, 'openURL')
.mockImplementation(() => Promise.resolve(true));
return { canOpenURL, openURL };
}
The problem is that openURL is not been called.
Here is the test:
test('should open url when there is a proper app the open it', async () => {
const { canOpenURL, openURL } = mockSuccessLinking();
const { result } = renderHook(() =>
useApplyToJob('https://www.google.com/'),
);
const [apply] = result.current;
// Act
apply();
// Assert
expect(result.current[1].error).toBeNull();
expect(canOpenURL).toHaveBeenCalled();
expect(openURL).toHaveBeenCalled();
});
And this the hook under test:
export function useApplyToJob(url) {
const [error, setError] = useState(null);
const apply = () => {
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
} else {
setError(`Don't know how to open ${url}`);
}
});
};
return [apply, { error }];
}
Given canOpenURL returns a promise, you'll need to wait for the async to occur before testing if openURL has been called. react-hooks-testing-library ships a few async utils to help with this.
Generally it's preferred to use waitForNextUpdate or waitForValueToChange as they are a bit more descriptive of what the test is waiting for, but your hook is not updating any state in the successful case, so you will need to use the more general waitFor utility instead:
test('should open url when there is a proper app the open it', async () => {
const { canOpenURL, openURL } = mockSuccessLinking();
const { result, waitFor } = renderHook(() =>
useApplyToJob('https://www.google.com/'),
);
const [apply] = result.current;
// Act
apply();
// Assert
expect(result.current[1].error).toBeNull();
expect(canOpenURL).toHaveBeenCalled();
await waitFor(() => {
expect(openURL).toHaveBeenCalled();
});
});
As a side note, destructuring result.current to access apply is not recommended. It may work now, but it does not take much refactoring before the apply you're calling is using stale values from a previous render.
Similarly, I'd recommend wrapping the apply() call in act, even though it does not update any state right now. It just makes refactoring easier in the future as well as keeping your tests more consistent when you're testing the error case (which will need an act call).
import { renderHook, act } from '#testing-library/react-hooks';
// ...
test('should open url when there is a proper app the open it', async () => {
const { canOpenURL, openURL } = mockSuccessLinking();
const { result, waitFor } = renderHook(() =>
useApplyToJob('https://www.google.com/'),
);
// Act
act(() => {
result.current[0]();
});
// Assert
expect(result.current[1].error).toBeNull();
expect(canOpenURL).toHaveBeenCalled();
await waitFor(() => {
expect(openURL).toHaveBeenCalled();
});
});
I want to test with testcafe if a function on a window object is executed with certain parameters. Is it possible with Testcafe?
The function call looks like this:
window.myObject.myFunction({customObject: true});
You can use the ClientFunction API to create a spy function in a window object. Please look at the following test example:
import { ClientFunction } from 'testcafe';
fixture `New Fixture`
.page `https://cf51n.csb.app/`;
const spyOn = ClientFunction(() => {
// Create an array where we store information about `myFunction` calls
window.myFunctionSpyData = [];
// Store the original `myFunction` value
window.orirginalFunction = window.myObject.myFunction;
window.myObject.myFunction = function() {
// Save data about the current call
window.myFunctionSpyData.push(...arguments);
// Call the original `myFunction` value
window.orirginalFunction(...arguments);
}
});
const getSpyData = ClientFunction(() => {
// Retrieve data about myFunction calls from client
return window.myFunctionSpyData;
});
const spyOff = ClientFunction(() => {
// Restore the original myFunction value
window.myObject.myFunction = window.orirginalFunction;
delete window.spyData;
});
test('New Test', async t => {
await spyOn();
await t.click('#btn');
const data = await getSpyData();
await spyOff();
await t
.expect(data.length).eql(2)
.expect(data[0]).eql('1')
.expect(data[1]).eql('2');
});
I am trying to debounce a method within a Vuex action that requires an external API.
// Vuex action:
async load ({ state, commit, dispatch }) {
const params = {
period: state.option.period,
from: state.option.from,
to: state.option.to
}
commit('SET_EVENTS_LOADING', true)
const res = loadDebounced.bind(this)
const data = await res(params)
console.log(data)
commit('SET_EVENTS', data.collection)
commit('SET_PAGINATION', data.pagination)
commit('SET_EVENTS_LOADING', false)
return data
}
// Debounced method
const loadDebounced = () => {
return debounce(async (params) => {
const { data } = await this.$axios.get('events', { params })
return data
}, 3000)
}
The output of the log is:
[Function] {
cancel: [Function]
}
It is not actually executing the debounced function, but returning to me another function.
I would like to present a custom debounce method which you can use in your vuex store as
let ongoingRequest = undefined;
const loadDebounced = () => {
clearTimeout(ongoingRequest);
ongoingRequest = setTimeout(_ => {
axios.get(<<your URL>>).then(({ data }) => data);
}, 3000);
}
This method first ensures to cancel any ongoing setTimeout in the pipeline and then executes it again.
This can be seen in action HERE
I created a recordSaga function, its target is to record what actions have been dispatched during the saga.
export const recordSaga = async (saga, initialAction, state) => {
const dispatched = [];
const done = await runSaga(
{
dispatch: action => dispatched.push(action),
getState: () => state,
},
saga,
initialAction,
).done;
return {
dispatched,
done,
};
};
so let's say my saga is this one
export function* mySaga() {
const needToSave = yield select(needToSaveDocument);
if (needToSave) {
yield put(saveDocument());
yield take(SAVE_DOCUMENT_SUCCESS);
}
yield put(doSomethingElse())
}
I want to write two tests, which I expect to be the following
describe('mySaga', async () => {
it('test 1: no need to save', async () => {
const state = { needToSave: false }
const { dispatched } = await recordSaga(mySaga, {}, state);
expect(dispatched).toEqual([
doSomethingElse()
])
})
it('test 2: need to save', async () => {
const state = { needToSave: true }
const { dispatched } = await recordSaga(mySaga, {}, state);
expect(dispatched).toEqual([
saveDocument(),
doSomethingElse()
])
})
})
However, for the test 2 where there is a take in between, and of course jest (or its girlfriend jasmine) is yelling at me: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
I know it is because runSaga is waiting for the take(SAVE_DOCUMENT_SUCCESS), but how can I mock that up ?
The answer stdChannel().put({type, payload})
Why ?
Using stdChannel you can dispatch after the first run.
How ?
import stdChannel;
add to the first param in runSaga;
call stdChannel().put(SAVE_DOCUMENT_SUCCESS);
Example
what worked for me
I left the first test as it is the expected final result, but the solution comes on the last 2.
import { runSaga, stdchannel } from 'redux-saga'
let dispatchedActions = [];
let channel;
let fakeStore;
beforeEach(() => {
channel = stdChannel(); // you have to declare the channel to have access to it later
fakeStore = {
channel, // add it to the store in runSaga
getState: () => "initial",
dispatch: (action) => dispatchedActions.push(action),
};
});
afterEach(() => {
global.fetch.mockClear();
});
it("executes getData correctly", async () => {
await runSaga(fakeStore, getData, getAsyncData("test")).toPromise();
expect(global.fetch.mock.calls.length).toEqual(1);
expect(dispatchedActions[0]).toEqual(setData(set_value));
});
it("triggers takeLatest and call getData(), but unfortunately doesn't resolve promise", async () => {
await runSaga(fakeStore, rootSaga)// .toPromise() cannot be used here, as will throw Timeout error
channel.put(getAsyncData("test")); // if remove this line, the next 2 expects() will fail
expect(global.fetch.mock.calls.length).toEqual(1);
// expect(dispatchedActions[1]).toEqual(setData(set_value)); // will fail here, but pass on the next it()
});
it("takes the promised data from test above", () => {
expect(dispatchedActions[1]).toEqual(setData(set_value));
});
this answer (about true code, not tests) helped me
By looking at recordSaga:
export const recordSaga = async (saga, initialAction, state) => {
It seems that you should pass {type: SAVE_DOCUMENT_SUCCESS} as a second argument (i.e initialAction). That should trigger the take effect.
Good day. I have the following problem:
I have an item editor.
How it works: I push 'Add' button, fill some information, click 'Save' button.
_onSaveClicked function in my react component handles click event and call function from service, which sends params from edit form to server and return promise.
_onSaveClicked implements
.then(response => {
console.log('I\'m in then() block.');
console.log('response', response.data);
})
function and waits for promise result. It works in real situation.
I created fake service and placed it instead of real service.
Service's function contains:
return Promise.resolve({data: 'test response'});
As you can see fake service return resolved promise and .then() block should work immediatly. But .then() block never works.
Jest test:
jest.autoMockOff();
const React = require('react');
const ReactDOM = require('react-dom');
const TestUtils = require('react-addons-test-utils');
const expect = require('expect');
const TestService = require('./service/TestService ').default;
let testService = new TestService ();
describe('TestComponent', () => {
it('correct test component', () => {
//... some initial code here
let saveButton = TestUtils.findRenderedDOMComponentWithClass(editForm, 'btn-primary');
TestUtils.Simulate.click(saveButton);
// here I should see response in my console, but I don't
});
});
React component save function:
_onSaveClicked = (data) => {
this.context.testService.saveData(data)
.then(response => {
console.log('I\'m in then() block.');
console.log('response', response.data);
});
};
Service:
export default class TestService {
saveData = (data) => {
console.log('I\'m in services saveData function');
return Promise.resolve({data: data});
};
}
I see only "I'm in services saveData function" in my console.
How to make it works? I need to immitate server response.
Thank you for your time.
You can wrap your testing component in another one like:
class ContextInitContainer extends React.Component {
static childContextTypes = {
testService: React.PropTypes.object
};
getChildContext = () => {
return {
testService: {
saveData: (data) => {
return {
then: function(callback) {
return callback({
// here should be your response body object
})
}
}
}
}
};
};
render() {
return this.props.children;
}
}
then:
<ContextInitContainer>
<YourTestingComponent />
</ContextInitContainer>
So your promise will be executed immediately.