Mock specific graphql request in cypress when running e2e tests - graphql

When running e2e tests with Cypress, my goal is to mock a specific graphql query.
Currently, I can mock all requests like this:
cy.server();
cy.route('POST', '/graphql', {
data: {
foo: 'bar'
},
});
The problem is that this mocks all /graphql queries. It would be awesome if I somehow could say:
cy.route('POST', '/graphql', 'fooQuery', {
data: {
foo: 'bar'
},
});
In our application, we are using Apollo Graphql - and thus all queries are named.

With cypress 6.0 route and route2 are deprecated, suggesting the use of intercept. As written in the docs (https://docs.cypress.io/api/commands/intercept.html#Aliasing-individual-GraphQL-requests) you can mock the GraphQL requests in this way:
cy.intercept('POST', '/api', (req) => {
if (req.body.operationName === 'operationName') {
req.reply({ fixture: 'mockData.json'});
}
}

One way to go about it is to provide the mocked data for the graphql operations in question inside one fixture file
cypress/support/commands.js
Cypress.Commands.add('stubGraphQL', (graphQlFixture) => {
cy.fixture(graphQlFixture).then((mockedData) => {
cy.on('window:before:load', (win) => {
function fetch(path, { body }) {
const { operationName } = JSON.parse(body)
return responseStub(mockedData[operationName])
}
cy.stub(win, 'fetch', fetch).withArgs("/graphql").as('graphql');
});
})
})
const responseStub = result => Promise.resolve({
json: () => Promise.resolve(result),
text: () => Promise.resolve(JSON.stringify(result)),
ok: true,
})
//TODO how to get it to stop listening and trying to stub once the list of operations provided in fixture have been stubbed?
example fixture file cypress/fixtures/signInOperation.json (note that there are 2 operations in there and that's how you can specify which response to mock)
{
"SIGNIN_MUTATION": {
"data":{"signin":{"id":"ck896k87jac8w09343gs9bl5h","email":"sams#automation.com","name":"Sam","__typename":"User"}}
},
"CURRENT_USER_QUERY" : {
"data":{"me":{"id":"ck896k87jac8w09343gs9bl5h","email":"sams#automation.com","name":"!!Sam's Mock","permissions":["USER"],"cart":[{"id":"ck89gebgvse9w0981bhh4a147","quantity":5,"item":{"id":"ck896py6sacox0934lqc8c4bx","price":62022,"image":"https://res.cloudinary.com/deadrobot/image/upload/v1585253000/sickfitz/ecgqu4i1wgcj41pdlbty.jpg","title":"MensShoes","description":"Men's Shoes","__typename":"Item"},"__typename":"CartItem"},{"id":"ck89gec6mb3ei0934lmyxne52","quantity":5,"item":{"id":"ck896os7oacl90934xczopgfa","price":70052,"image":"https://res.cloudinary.com/deadrobot/image/upload/v1585252932/sickfitz/i7ac6fqhsebxpmnyd2ui.jpg","title":"WomensShoes2","description":"Women's Shoes","__typename":"Item"},"__typename":"CartItem"},{"id":"ck89gl45psely0981b2bvk6q5","quantity":7,"item":{"id":"ck89ghqkpb3ng0934l67rzjxk","price":100000,"image":"https://res.cloudinary.com/deadrobot/image/upload/v1585269417/sickfitz/eecjz883y7ucshlwvsbw.jpg","title":"watch","description":"Fancy Watch","__typename":"Item"},"__typename":"CartItem"}],"__typename":"User"}}
}
}
in your spec file
cy.stubGraphQL('signInOperation.json')
cy.visit(yourURL)
cy.get(loginButton).click()

With cypress 5.1, using the new route2 command it is very simple to mock GraphQL requests, for example:
cy.route2('/graphql', (req) => {
if(req.body.includes('operationName')){
req.reply({ fixture: 'mockData.json'});
}
});
I just added an if condition to evaluate if the body of the GraphQL request contains certain string as part of the query.
If that is true, then I reply back with a custom body loaded from a fixture.
Documentation for cy.route2():
https://docs.cypress.io/api/commands/route2.html

You can try this if want to use fixture for graphql response:
cy.intercept('POST', '/test_api/graphql', (req) => {
req.continue((res) => {
if (req.body.operationName === 'op_name') {
res.send({ fixture: 'MyFixture/xyz.json' }),
req.alias = 'graphql'
}
})
})

Related

How to test dispatched react function using Jest

I am trying to unit test a function which makes an async call using an Axios helper instance. I have attempted multiple ways of trying to unit test this but I can not seem to find any material online which has helped. I've been stuck on this problem for a few days which is frustrating so any help would be appreciated! Below are the Axios Helper file (api.js)
api.js
import axios from 'axios'
const API = (token = null) => {
let headers = {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-key': process.env.NEXT_PUBLIC_API_HEADER_SUBSCRIPTION_KEY
}
if (token) {
const tokenHeader = { Authorization: 'Bearer ' + token }
headers = { ...headers, ...tokenHeader }
}
const url = process.env.NEXT_PUBLIC_API_BASE_URL
const API = axios.create({
baseURL: url,
headers
})
return API
}
export default API
mocked API
export default {
post: jest.fn(() =>
Promise.resolve({
data: {}
})
),
get: jest.fn(() =>
Promise.resolve({
data: {}
})
)
}
action file
export const initiate2FA = (destinationValue) => async () => {
const twoFactorAuth = destinationValue
const res = await API().post('/foo', {
Destination: twoFactorAuth
})
return res
}
Action.test.js
import API from 'api/api'
import { initiate2FA } from 'actions/userActions'
jest.mock('api/api')
const mockedAxios = API
const dispatch = jest.fn()
describe('Initiate2FA function', () => {
it('bar', async () => {
mockedAxios.get.mockImplementationOnce(() => Promise.resolve({ status: 200 }))
const t = await dispatch(initiate2FA('test#test.com'))
console.log(t)
})
})
My issue with the above test file is that it returns an anonymous function and I do not know how to handle this to pass the unit test. The goal of the test is to make sure the function is called. I am not sure if I am approaching this the correct way or should change my approach.
Again, any suggestions would be great!
Mocking an API call is something you can mock on your own React component, instead of a function, and the best option would be to not mock anything on your component. Here you can read all about why you should not mock your API functions. At the end of the article, you're going to find a library called Mock Service Worker which you can use for your purpose.
The way you declare you have an actual HTTP called that needs to be mocked would be something like this:
rest.get('/foo', async (req, res, ctx) => {
const mockedResponse = {bar: ''};
return res(ctx.json(mockedResponse))
}),
If you just need to unit test a function, you can still use Mock Service Worker to resolve the HTTP request, and then test what happens after that. This would still be your first choice. And the test would look like:
// this could be in another file or on top of your tests.
rest.get('/foo', async (req, res, ctx) => {
const mockedResponse = {bar: ''};
return res(ctx.json(mockedResponse))
}),
// and this would be your test
describe('Initiate2FA function', () => {
it('bar', async () => {
const res = await initiate2FA('test#test.com');
expect(res).toBe({bar: '');
})
})

Apollo Server Health Check custom response

I am currently looking to provide more information to the health check other than status: pass. Is this possible? I tried sending test strings unfortunately, I am still seeing the same response json. Thank you in advance!
Code:
onHealthCheck: (req) => {
return new Promise((resolve, reject) => {
if(true) {
console.log(req);
resolve(req);
//resolve("test")
} else {
reject();
}
})
}
onHealthCheck implementation looks like this (express is used):
if (onHealthCheck) {
onHealthCheck(req)
.then(() => {
res.json({ status: 'pass' });
})
.catch(() => {
res.status(503).json({ status: 'fail' });
});
}
so as you can see, it returns hardcoded value.
You can always implement your custom health check also using express.

How to stub a call to graphql using cypress?

I'm writing a Vue app that uses vue-apollo to interact with graphql. I'm wondering if it's possible to stub the graphql requests. I thought this should work:
it('should access a story', function() {
cy.server();
cy.route('http://localhost:3002/graphql', {
data: {
Story: { id: 2, title: 'story title', content: 'story content' }
}
});
cy.visit('/stories/2');
});
Unfortunately, I get an error from graphql complaining that id is an Int instead of an ObjectId. Am I missing something?
The problem was that stubbing fetch requests isn't yet implemented in Cypress (which is what Vue Apollo is using). I ended up following these instructions:
Install github/fetch
Add this to cypress/support/index.js:
.
Cypress.on('window:before:load', win => {
win.fetch = null;
win.Blob = null;
});
Now it works!
I got it working with this package here:
npm i #iam4x/cypress-graphql-mock
Add this line to 'support/commands.js'
import "#iam4x/cypress-graphql-mock";
go to your graphiql playground and download your schema
add task command to 'plugins/index.js' (REMEMBER TO CHANGE PATH TO SCHEMA FILE YOU DOWNLOADED EARLIER)
module.exports = (on, config) => {
on("task", {
getSchema() {
return fs.readFileSync(
path.resolve(__dirname, "../../../schema.graphql"),
"utf8"
);
}
});
};
write your tests with loaded schema
beforeEach(() => {
cy.server();
cy.task("getSchema").then(schema => {
cy.mockGraphql({
schema
});
});
});`
describe("Login Form", () => {
it("should redirect after login", () => {
cy.mockGraphqlOps({
operations: {
Login: {
login: {
jwt: "some-token",
user: {
id: "5d5a8e1e635a8b6694dd7cb0"
}
}
}
}
});
cy.visit("/login");
cy.getTestEl("email-input").type("Max Mustermann");
cy.getTestEl("password-input").type("passwort");
cy.getTestEl("submit").click();
cy.getTestEl("toolbar-title").should("exist");
});
})
Visit the original repo for further explanation as i find it less confusing. The package you have installed is just a working fork of this one:
https://github.com/tgriesser/cypress-graphql-mock

Testing ngrx Effects with Jasmine spy

I am writing an ngrx effect and trying to test it. However, the effect calls a service that calls an API that will require authentication. As a result, I am trying to create a spy in Jasmine to handle returning the data. This is my first time using ngrx effects, so I am really unsure where to put different parts of the code. Nothing I have done is allowing this test to run correctly.
The effect is a very simple one as follows:
#Effect() itemSelected: Observable<Action> = this.d.pessimisticUpdate('ITEM_SELECTED', {
run: (action: ItemSelected) => {
return this.myService.getItemById(action.payload).map((res) => ({
type: 'ITEM_INFO_RETURNED',
payload: res
}));
},
onError: (a: ItemSelected, error) => {
console.error('Error', error);
}
});
constructor(private d: DataPersistence<ItemState>, private myService: MyService) {
// add auth headers here
}
My test is currently written as follows:
describe('ItemEffects', () => {
let actions: Observable<any>;
let effects: ItemEffects;
let myService = jasmine.createSpyObj('MyService', ['getItemById']);
let item1: Item = {id: 1, name: 'Item 1'};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({}),
],
providers: [
ItemEffects,
DataPersistence,
provideMockActions(() => actions),
{
provide: MyService,
useValue: myService
}
],
});
effects = TestBed.get(ItemEffects);
});
describe('someEffect', () => {
it('should work', async () => {
myService.getItemById.and.callFake(function (id) {
return items.find((r) => r.id === id);
});
actions = hot('-a-|', { a:{ type:'ITEM_INFO_RETURNED', payload:1}});
expect(effects.itemSelected).toEqual(
{ type: 'ITEM_INFO_RETURNED', payload: { item1 } }
);
});
});
});
This is still attempting to use the production MyService (requiring authentication). If I move the myService override out of the provider and into the actual test,
TestBed.overrideProvider(MyService, { useValue: myService });
I get an error that it cannot read the property "itemSelected" of undefined, which would be when I am calling the effects at the very end of the test.
I am really new to ngrx, as well as to TestBed. Is there somewhere else I should be defining this Jasmine spy? Should I be using something other than createSpyOn for this?
Thanks in advance!

Back-end tests with elasticsearch fails without setTimeout

I am writing tests for back-end which uses MongoDB and Elasticsearch. The problem is that without wrapping test with setTimeout test fails, and it looks like elasticsearch can't index mock data into db before test. Here is the code:
let elasticSearch = require('elasticsearch');
let elasticClient = new elasticSearch.Client({
host: 'localhost:9200'
});
let app = require('./dist/app'); //path to my application
let supertest = require('supertest');
before((done) => {
elasticClient.index(elasticMockData, function() {
done();
});
});
beforeEach(() => {
request = supertest(app);
});
it('should get data from elastic', () => {
setTimeout(() => { // if I comment this timeout, test will fail
request.get('/api/elastic')
.end((err, res) => {
expect(res.body.hits.hits.length).not.to.equal(0);
})
}, 1000); // if I comment this timeout, test will fail
});
I think you will agree that timeout is not an elegant and nice solution, which slows every test to 1 second or more. Maybe, am I missing something?
Found a solution, maybe it will be useful for someone.
According to Elasticsearch docs:
By default, the document will be available for get() actions immediately, but will only be available for searching after an index refresh (which can happen automatically or manually).
So, in this case, done() should be called within another callback function:
before((done) => {
elasticClient.index(elasticMockData, function() {
elasticClient.indices.refresh(function (err: any, res: any) {
if (err) {
return;
}
done();
});
});
});

Resources