In a Cypress test how can I stub argument of a function - cypress

I am writing a Cypress integration test for a Google Optimize experiment and I would like to stub the experiment variant value provided by gtag library.
The value is provided as the argument of callback for this function:
gtag('event', 'optimize.callback', {
name: '<experiment_id_A>',
callback: (value) => 'unknown',
});
How can I stub argument "value"? (without stubbing the callback return)
--
Docs for:
Google Optimize method
Cypress stub
Sinon stub

I don't know much about gtag, but looking at the reference page it may be possible to stub if you are using the "external" function pattern shown on that page rather than the "inline" function you have above.
The way to stub internal functions in a React or other framework is to expose it on the window object.
function implementExperimentA(value) {
if (value == '0') {
// Provide code for visitors in the original.
} else if (value == '1') {
// Provide code for visitors in first variant.
} else if (value == '2') {
// Provide code for visitors in section variant.
}
...
}
if (window.Cypress) {
// Cypress is active
console.log('referencing "implementExperimentA" on window')
window.implementExperimentA = implementExperimentA;
}
gtag('event', 'optimize.callback', {
name: '<experiment_id_A>',
callback: implementExperimentA
})
Then in the test, you might set up the stub in the beforeLoad event
cy.visit('http://localhost:3000', {
onBeforeLoad(win) {
// Stub your functions here
cy.stub(win, 'implementExperimentA').callsFake((realValue) => {
const mockValue = 'abc'
console.log(`implementExperimentA called with ${realValue}, subbing ${mockValue}`)
return window.implementExperimentA(mockValue)
})
},
})
This should instantiate the stub at the earliest point in the page load, provided the reference has been set up.
Check the console.logs are firing in the right order, if not then there might be a delay in referencing the function and you can choose the stub point with cy.window().
cy.visit('http://localhost:3000')
...
cy.window().then(win => {
// Stub your functions here
cy.stub(win, 'implementExperimentA').callsFake((realValue) => {
const mockValue = 'abc'
console.log(`implementExperimentA called with ${realValue}, subbing ${mockValue}`)
return window.implementExperimentA(mockValue)
})
})

Related

Cypress cy.wait(...) response body is undefined despite setting up fixture in intercept command

I have created Cypress e2e tests that use the following functions:
to mock the responses
export function getUserAndSupplier(): void {
cy.intercept('GET', `${Cypress.env('BaseUrl')}/users/me`,
{
fixture: 'shared/Users/me.json',
})
.as('users');
cy.intercept('GET', `${Cypress.env('BaseUrl')}/users/me/supplier`,
{
fixture: 'shared/Suppliers/supplier.json',
})
.as('supplier');
}
to check if responses are in accordance to the fixtures:
export function checkUserAndSupplier(): void {
cy.wait('#users')
.its('response.body')
.should('not.be.undefined')
.then((interception: any) => {
//assertions on each field
});
cy.wait('#supplier')
.its('response.body')
.should('not.be.undefined')
.then((interception: GetCurrentSupplierResponse) => {
//assertions on each field
});
}
Tests have Cucumber preprocessor implemented, the GIVEN and WHEN steps definition for given test are:
beforeEach(() => {
// intercept user and supplier api
getUserAndSupplier();
// intercept GET /paymentProviders
interceptPaymentProviders();
});
Given('User navigates to the {string} page', () => {
cy.visit('/sell/payment-providers');
// assert api calls on user and supplier
checkUserAndSupplier();
});
When('User clicks on {string} button', () => {
getActivationButton()
.scrollIntoView()
.contains('Activate')
.should('be.visible')
.and('not.be.disabled')
.click();
// Ensure Continue Button is disabled
getContinueButton()
.should('be.visible')
.and('be.disabled');
});
while the .feature file test is:
Scenario: Happy path - activate payment method
Given User navigates to the "sell/payment-providers" page
When User clicks on "activate" button
Then User is able to successfully activate payment provider
The problem is that sometimes, despite having responses mocked using fixtures (they're not null or empty), 'response.body' property is undefined, which makes tests flaky.
At the beginning I thought I have some asynchronous functions that lack await and make response.body being undefined, but this was not the case.
What may be the cause of this? And what makes it sometimes working, and sometimes not?
It's hard to tell what exactly is going on, the code looks ok.
Here's some general tips to try.
Cache
It's possible the browser cache is interfering with the intercept. To avoid caching, add this
beforeEach(() => {
Cypress.automation('remote:debugger:protocol', {
command: 'Network.clearBrowserCache'
})
...
})
Debug call sequence
To debug the network calls, combine the two intercepts and use callbacks to console.log what gets intercepted.
If something is changing the order of response, the problem may be caused by the sequence of cy.wait('#users') followed by cy.wait('#supplier') so combining the intercepts will catch that.
export function getUserAndSupplier(): void {
cy.intercept('/users*', (req) => {
if (req.url.endsWith('/me')) {
console.log('users request', req)
req.alias = 'users'
req.reply({fixture: 'shared/Users/me.json'})
}
if (req.url.endsWith('/me/supplier')) {
console.log('supplier request', req)
req.alias = 'supplier'
req.reply({fixture: 'shared/Suppliers/supplier.json'})
}
})
}
Or use a single alias for both paths and check inside the interception.
export function getUserAndSupplier(): void {
cy.intercept('/users*', (req) => {
if (req.url.endsWith('/me')) {
req.reply({fixture: 'shared/Users/me.json'})
}
if (req.url.endsWith('/me/supplier')) {
req.reply({fixture: 'shared/Suppliers/supplier.json'})
}
})
.as('both')
}
export function checkUserAndSupplier(): void {
const checkInterception = (interception) => {
if (interception.request.url.endsWith('/me')) {
console.log('users response', interception.response)
// assertions for users
}
if (interception.request.url.endsWith('/me/supplier')) {
console.log('supplier response', interception.response)
// assertions for supplier
}
}
cy.wait('#both').then(checkInterception); // first interception
cy.wait('#both').then(checkInterception); // second interception
}
Lastly, something in interceptPaymentProviders() is interfering with the other intercepts.

Testing an Async function using Jest / Enzyme

Trying run a test case for the following:
async getParents() {
const { user, services, FirmId } = this.props;
let types = await Models.getAccounts({ user, services, firmId: FirmId });
let temp = types.map((type) => {
if(this.state.Parent_UID && this.state.Parent_UID.value === type.Account_UID) {
this.setState({Parent_UID: {label: type.AccountName, value: type.Account_UID}})
}
return {
label: type.AccountName,
value: type.Account_UID,
}
})
this.setState({ParentOptions: temp});
}
here is what i have so far for my test:
beforeEach(() => wrapper = mount(<MemoryRouter keyLength={0}><AccountForm {...baseProps} /></MemoryRouter>));
it('Test getParents function ',async() => {
wrapper.setProps({
user:{},
services:[],
FirmId:{},
})
wrapper.find('AccountForm').setState({
SourceOptions:[[]],
Parent_UID: [{
label:[],
value:[],
}],
});
wrapper.update();
await
expect(wrapper.find('AccountForm').instance().getParents()).toBeDefined()
});
If i try to make this ToEqual() it expects a promise and not anobject, what else could I add into this test to work properly.
Goal: Make sure the functions gets called correctly. The test is passing at the moment and has a slight increase on test coverage.
Using Jest and Enzyme for React Js
you can put the await before the async method, like:
await wrapper.find('AccountForm').instance().getParents()
and compare if the state was changed.
In another way, if can mock your API request, because this is a test, then you do not need the correct API, but know if the function calls the API correctly and if the return handling is correct.
And, you cand spy the function like:
const spy = jest.spyOn(wrapper.find('AccountForm').instance(), 'getParents');
and campare if the function was called if they are triggered by some action:
expect(spy).toBeCalled()

.then is not a function Angularjs factory

I just started learning Jasmine test cases for angularjs. I am unable to test below code.Kindly help
$scope.getConstants = function(lovName) {
ConstantService.getConstants(lovName).then(function(d) {
switch (lovName) {
case 'WORKFLOW':
$scope.workflowTypes = d;
$scope.loadCounterpartyTmp();
break;
--------Other Cases
}
My ConstantService is defined as
App.factory('ConstantService', [ '$http', '$q', function($http, $q) {
return {
getConstants : function(lovName) {
return $http.post('/sdwt/data/getConstants/', lovName).then(function(response) {
return response.data;
}, function(errResponse) {
return $q.reject(errResponse);
});
}
I want to test getConstants function.I need to create a mock of ConstantService and pass the data to it.
I have written below test case but the test case is not working.Please let me know how to test the above code
describe('getConstantsForMurexEntity', function() {
it('testing getConstantsForMurexEntity function', function() {
var d=[];
d.push(
{id:1,value:'ABC'},
{id:2,value:'DEF'},
{id:3,value:'IJK'},
{id:4,value:'XYZ'},
);
//defined controller
spyOn(ConstantService, 'getConstants').and.returnValue(d);
$scope.getConstants('WORKFLOW');
expect($scope.workflowTypes).toBe(d);
The above test case is not working as it is saying "ConstantService.getConstants(...).then is not a function".
Your ConstantService.getConstants() function returns a promise, which your actual code is using, with the .then() call. This means means that when you spy on it, you also need to return a promise, which you are not doing. Because you are not returning a promise, when your actual call tries to call .then(), it is undefined, which is the reason for the error message.
Also, you aren't using Array.push correctly.
Your test should probably look something like the following (note, this is untested):
describe('getConstantsForMurexEntity', function() {
it('should set workflowTypes to the resolved value when lovName is "WORKFLOW"', inject(function($q) {
var deferred = $q.defer();
spyOn(ConstantService, 'getConstants').and.returnValue(deferred.promise);
var d = [
{id:1,value:'ABC'},
{id:2,value:'DEF'},
{id:3,value:'IJK'},
{id:4,value:'XYZ'},
];
$scope.getConstants('WORKFLOW');
deferred.resolve(d);
$scope.$apply();
expect($scope.workflowTypes).toBe(d);
}));
});

How to exit Protractor test from Spec on specific condition?

I have a suite which includes multiple specs. Each spec uses code on some libraries that return a rejected promise upon failure.
I can easily catch those rejected promises inside my spec. What I'm wondering about is that if I can make Protractor exit the whole suite inside that catch function, because the next specs inside the same suite are dependent on the success of the previous specs.
Pretend I have a suite called testEverything which has these specs openApp,signIn,checkUser,logout. If openApp fails, all next specs will fail due to dependency.
Consider this code for openApp:
var myLib = require('./myLib.js');
describe('App', function() {
it('should get opened', function(done) {
myLib.openApp()
.then(function() {
console.log('Successfully opened app');
})
.catch(function(error) {
console.log('Failed opening app');
if ( error.critical ) {
// Prevent next specs from running or simply quit test
}
})
.finally(function() {
done();
});
});
});
How would I exit the whole test?
There is a module for npm called protractor-fail-fast. Install the module npm install protractor-fail-fast. Here's an example from their site where you would place this code into your conf file:
var failFast = require('protractor-fail-fast');
exports.config = {
plugins: [{
package: 'protractor-fail-fast'
}],
onPrepare: function() {
jasmine.getEnv().addReporter(failFast.init());
},
afterLaunch: function() {
failFast.clean(); // Cleans up the "fail file" (see below)
}
}
Their url is here.
I have managed to come up with a workaround. Now the actual code that I used is way more complex, but the idea is the same.
I added a global variable inside protractor's config file called bail. Consider the following code at the top of config file:
(function () {
global.bail = false;
})();
exports.config: { ...
The above code uses an IIFE (Immediately Invoked Function Expression) which defines bail variable on protractor's global object (which would be available throughout the whole test).
I also have written asynchronous wrappers for the Jasmine matchers I need, which would execute an expect expression followed by a comparison, and return a promise (using Q module). Example:
var q = require('q');
function check(actual) {
return {
sameAs: function(expected) {
var deferred = q.defer();
var expectation = {};
expect(actual).toBe(expected);
expectation.result = (actual === expected);
if ( expectation.result ) {
deferred.resolve(expectation);
}
else {
deferred.reject(expectation);
}
return deferred.promise;
}
};
}
module.exports = check;
Then at the end of each spec, I set the bail value based on the spec's progress, which would be determined by the promise of those asynchronous matchers. Consider the following as first spec:
var check = require('myAsyncWrappers'); // Whatever the path is
describe('Test', function() {
it('should bail on next spec if expectation fails', function(done) {
var myValue = 123;
check(myValue).sameAs('123')
.then(function(expectation) {
console.log('Expectation was met'); // Won't happen
})
.catch(function(expectation) {
console.log('Expectation was not met'); // Will happen
bail = true; // The global variable
})
.finally(function() {
done();
});
});
});
Finally, on the beginning of next specs, I check for bail and return if necessary:
describe('Test', function() {
it('should be skipped due to bail being true', function(done) {
if ( bail ) {
console.log('Skipping spec due to previous failure');
done();
return;
}
// The rest of spec
});
});
Now I want to mention that there's one module out there called protractor-fail-fast which bails on the whole test whenever an expectation fails.
But in my case, I needed to set that bail global variable depending on which type of expectation has been failed. I ended up writing a library (really small) that distinguishes failures as critical and non-critical and then using that, specs would be stopped only if a critical failure has occurred.

Async call in beforeAll

Here are 2 samples of the same test. The only difference is that first one uses a promise in beforeAll block to assign a value to the variable while the second one assigns the value directly.
I raised a similar question Running spec after promise has been resolved with one of the comments pointing to this issue https://github.com/jasmine/jasmine/issues/412 which says that this is not supported in Jasmine. Has somebody figured out any workaround?
This fails with TypeError: Cannot read property 'forEach' of undefined
describe('Async car test', function () {
var cars;
beforeAll(function (done) {
// getCars() is a promise which resolves to ['audi', 'bmw']
getCars().then(function (data) {
cars = data;
console.log(cars) // ['audi', 'bmw']
done();
});
});
cars.forEach(function (car) {
it('car ' + car, function () {
expect(car).toBe(car);
});
});
});
This works fine
describe('Car test', function () {
var cars = ['audi', 'bmw'];
cars.forEach(function (car) {
it('car ' + car, function () {
expect(car).toBe(car);
});
});
});
Posting it as an answer, because I can't see things properly in comments.
I'm actually generating tests in my spec as well, and I'm using https://www.npmjs.com/package/jasmine-data-provider , I think you probably cannot generate it directly from resolved promise. And wrapping in another it doesn't work for you. This should work:
var using = require('jasmine-data-provider');
using(cars.forEach, function (car) {
it(car + ' should be' + car, function () {
expect(car).toBe(car);
});
});
This is not an issue with jasmine, it is an issue with your code.
beforeAll does not block subsequent code below the statement. it blocks code that is defined in it('should ...', (done)=>{...});
it('should have cars', (done) => {
cars.forEach(function (car) {
expect(car).toBe(car);
});
});
Since Jasmine does not support adding tests at runtime, the trick is to request the asynchronous data before starting Jasmine, and then using the retrieved data during runtime instead. This can be achieved with a singleton and programmatically starting Jasmine.
See here for a working example.
// car-collection.js
class CarCollection {
static load() {
return this.request()
then((data) => this.cars = data);
}
static request() {
// in practice this function would do something cooler
return Promise.resolve(['audi', 'bmw']);
}
}
modules.export = CarCollection;
Since CarCollection has methods that are static they will be shared across imports and this.cars will persist.
// launcher.js
const Jasmine = require('jasmine');
const CarCollection = require('./car-collection');
CarCollection.load()
.then(() => {
console.log(`car count is ${CarCollection.cars.length}`); // prints: car count is 2
const jasmine = new Jasmine();
jasmine.loadConfigFile(...); // path to jasmine.json
jasmine.execute();
});
An important step here is configure jasmine to know where to look for the test files. Either by loading a config or passing specifics into the execute function.
// car.spec.js
const CarCollection = require('./car-collection');
describe('test', function () {
CarCollection.cars.forEach((car) => {
it('test' + car, () => {
expect(car).toBe(car);
});
});
});
Now run node ./launcher.js and the tests should run.

Resources