Code under test
// imports
const router = express.Router()
// This is what needs to be mocked
const client = new AwesomeGraphQLClient({
endpoint: process.env.GRAPHCMS_URL || '',
fetch,
fetchOptions: {
headers: {
authorization: `Bearer ${process.env.GRAPHCMS_TOKEN}`
}
}
})
interface LoginRequest {
email: string
password: string
}
router.post(
'/login',
async (req: Request<{}, {}, LoginRequest>, res: Response) => {
try {
const JWT_SECRET = getEnvironment('JWT_SECRET')
const { email, password } = req.body
if (!email || !password) {
res.status(400).json({
message: 'auth.provide.credentials',
full: 'You should provide an email and password'
})
return
}
if (!JWT_SECRET) {
res.status(500).json({
message: 'auth.secret.not.found',
full: 'Secret not found'
})
// TODO error logging
return
}
const { appUsers } = await client.request<
GetUserByEmailResponse,
GetUserByEmailVariables
>(getUserByEmailQuery, {
email
})
if (appUsers.length === 0) {
res.status(404).json({
message: 'auth.wrong.credentials',
full: 'You provided wrong credentials'
})
return
}
const user = appUsers[0]
const result: boolean = await bcrypt.compare(password, user.password)
if (result) {
var token = jwt.sign({ id: user.id, email: user.email }, JWT_SECRET)
res.status(200).json({
token
})
return
}
res.status(200).json({
message: 'auth.wrong.credentials',
full: 'You provided wrong credentials in the end'
})
} catch (e) {
console.log('E', e)
const error: ErrorObject = handleError(e)
res.status(error.code).json(error)
}
}
)
Tests for code above
import request from 'supertest'
import app from '../../../app'
import { mocked } from 'ts-jest/utils'
import { compare } from 'bcrypt'
import { AwesomeGraphQLClient } from 'awesome-graphql-client'
const mockRequestFn = jest.fn().mockReturnValue({
appUsers: [
{
id: 'tests'
}
]
})
jest.mock('awesome-graphql-client', () => ({
AwesomeGraphQLClient: jest.fn().mockImplementation(() => ({
request: mockRequestFn
}))
}))
I am trying to mock a method on a non default exported class from Awesome GraphQL. I also want to spy on this method, so I created a separate jest.fn() with a return value. The problem is that request is not a function: TypeError: client.request is not a function.
How can I mock and spy on the method of a mocked non default exported class?
SOLUTION
Managed to find a workaround. Make the method a function that returns the called mockRequest. This way you can spy on AwesomeGraphQLClient.request with mockRequest.toHaveBeenCalledTimes(x).
let mockRequest = jest.fn().mockReturnValue({
appUsers: [
{
id: 'tests'
}
]
})
jest.mock('awesome-graphql-client', () => {
return {
AwesomeGraphQLClient: jest.fn().mockImplementation(() => {
return {
request: () => mockRequest()
}
})
}
})
Related
I was working on a project using Firebase cloud messaging react. I was sending this to my server, but it doesn't work. Surely I have tried, but I don't know what's wrong again.
Below is the code.
Here it sends a POST request to Firebase, and it should send a notification to the user.
async function sendNotification(id, userMessage) {
const headers = {
'Authorization': `key=${code}`,
'Content-Type': 'application/json'
}
const message = {
'to': `${id}`,
'content_available': true,
'apns_priority': 5,
'notification': {
body: `${userMessage}`
},
const url = 'https://fcm.googleapis.com/fcm/send'
//console.log(code)
await axios.post(url, message, {
headers: headers
})
}
const sendMessageToServer = async (e) => {
//e.preventDefault();
toggle()
const docRe = doc(database, "help", mailer);
const data = {
email: user.email,
user: newMessage,
}
//console.log(data, 'not clear')
setNewMessage('')
//console.log(data, newMessage, 'cleared')
setShow(false)
if(newMessage === '') {
}
else {
const docRef = doc(database, "users", mailer);
await updateDoc(docRe, {
msg: arrayUnion(data)
})
.then(() => {
async function p() {
const id = await getDoc(docRef)
//console.log(id.data())
sendNotification(id.data().notice, `Admin : ${data.user}`)
}
p()
})
}
Sometimes it sends to my localhost because I tested there, but it doesn't work on my Netlify app. Secondly, I noticed that it keeps generating the same token for each user, but that's not the issue, but if you can help in both I would be grateful.
export default function Dashboard() {
async function callToken() {
await getToken(messaging, {vapidKey: process.env.NOTIFICATION})
.then((code) => {
//console.log(code)
async function docRef() {
const dc = doc(database, "users", auth.currentUser.email);
await updateDoc(dc, {
notice: code
});
}
docRef()
})
}
async function requestPermission() {
await Notification.requestPermission()
.then((permission) => {
if (permission === 'granted') {
console.log('Notification permission granted.')
callToken()
}
})
}
const goTo = useNavigate();
useEffect(() => {
onAuthStateChanged(auth, (data) => {
if(!data) {
goTo('/login')
}
else {
currentBalance();
requestPermission()
}
})
})
}
Please know I imported all required modules.
Debugger starts, .vscode/launch.json is set up. But I got this error when an api endpoint get called in Next.js:
API resolved without sending a response for /api/registerEmail, this may result in stalled requests.
I made this endpoint, something wrong here maybe? But the expected json is returned by the endpoint, strange.
import { NextApiRequest, NextApiResponse } from 'next'
import { connectToDatabase } from 'lib/connectToDatabase'
import { initUserDTO } from 'lib/initUserDTO'
import bcrypt from 'bcryptjs-react'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { mongoClient } = await connectToDatabase()
if (mongoClient) {
const db = mongoClient.db('tikex')
const collection = db.collection('users')
const u = await collection.findOne({
email: req.body.email,
fbId: null,
googleId: null,
})
if (u) {
res.status(400).json({ error: 'Email already exist' })
res.end()
} else {
bcrypt.hash(req.body.password, 10, async function (err, hash) {
const body = { ...req.body, password: hash }
await collection.insertOne(body)
const u = await collection.findOne({
email: req.body.email,
fbId: null,
googleId: null,
})
let u2 = await initUserDTO(u)
res.status(200).json(u2)
res.end()
})
}
} else {
res.status(400).json({ name: 'Database connection error' })
res.end()
}
}
https://codesandbox.io/s/login-authentication-usecontext-66t9t?file=/src/index.js
Here how we can pass token in headers for any other pages in codesandbox link. In my code i have action file like this. im getting my response in localstorage.how can i pass my accesstoken here as headers in this page.
import axios from 'axios';
export const upiAction = {
upi,
};
function upi(user) {
return (dispatch) => {
var data = {
upiId: user.upiId,
accountNumber: user.accountNumber,
};
axios
.post('http://localhost:9091/upiidcreation', data
)
.then((res) => {
console.log("res", (res));
const { data } = res;
alert(JSON.stringify(data.responseDesc));
// window.location.pathname = "./homes";
if (data.responseCode === "00") {
window.location.pathname = "./home"
}
})
.catch(err => {
dispatch(setUserUpiError(err, true));
alert("Please Check With details");
});
};
}
export function setUserUpi(showError) {
return {
type: 'SET_UPI_SUCCESS',
showError: showError,
};
}
export function setUserUpiError(error, showError) {
return {
type: 'SET_UPI_ERROR',
error: error,
showError: showError,
};
}
I have the following ErrorLink set for Apollo Client.
export const errorLink = onError(
({ response, graphQLErrors, networkError, operation }: ErrorResponse) => {
notificationService.notify("An Error Occurred");
},
);
I need to test this implementation in a unit test.
I've the following to test Apollo Links
const MockQuery = gql`
query {
foo
}
`;
interface LinkResult<T> {
operation: Operation;
result: FetchResult<T>;
}
async function executeLink<T = ApolloLink>(
linkToTest: ApolloLink,
request: GraphQLRequest = { query: MockQuery },
) {
const linkResult = {} as LinkResult<T>;
return new Promise<LinkResult<T>>((resolve, reject) => {
execute(ApolloLink.from([linkToTest]), request).subscribe(
(result) => {
linkResult.result = result as FetchResult<T>;
},
(error) => {
reject(error);
},
() => {
resolve(linkResult);
},
);
});
}
it('triggers a notification on error', () => {
const testLink = new ApolloLink(() => {
await waitFor(() => expect(notificationSpy).toBeCalledWith('An Error Occurred'))
return null;
});
const link = ApolloLink.from([errorLink, testLink]);
executeLink(link);
});
These unit test work fine for other links like AuthLink where I test whether the auth token was set to the localStorage. But I cannot test the error link because I cannot trigger a GraphQL error.
You can create a mocked terminating link and provide a GraphQL operation result.
E.g.
errorLink.ts:
import { onError } from '#apollo/client/link/error';
type ErrorResponse = any;
export const errorLink = onError(({ response, graphQLErrors, networkError, operation }: ErrorResponse) => {
console.log('An Error Occurred');
console.log('graphQLErrors: ', graphQLErrors);
});
errorLink.test.ts:
import { ApolloLink, execute, Observable } from '#apollo/client';
import { gql } from 'apollo-server-express';
import { errorLink } from './errorLink';
const MockQuery = gql`
query {
foo
}
`;
describe('68629868', () => {
test('should pass', (done) => {
expect.assertions(1);
const mockLink = new ApolloLink((operation) =>
Observable.of({
errors: [
{
message: 'resolver blew up',
},
],
} as any),
);
const link = errorLink.concat(mockLink);
execute(link, { query: MockQuery }).subscribe((result) => {
expect(result.errors![0].message).toBe('resolver blew up');
done();
});
});
});
test result:
PASS apollo-graphql-tutorial src/stackoverflow/68629868/errorLink.test.ts (5.02s)
68629868
✓ should pass (14ms)
console.log src/stackoverflow/68629868/errorLink.ts:6
An Error Occurred
console.log src/stackoverflow/68629868/errorLink.ts:7
graphQLErrors: [ { message: 'resolver blew up' } ]
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.067s
package version: #apollo/client#3.3.20
I specifically needed to test handling NetworkError with TypeScript and it was a right pain to figure out, so here's how you can do it:
import {
ApolloLink,
execute,
FetchResult,
from,
gql,
GraphQLRequest,
Observable,
Operation,
} from '#apollo/client'
import { errorLink, notificationService } from './'
interface LinkResult<T> {
operation: Operation
result: FetchResult<T>
}
const MockQuery = gql`
query {
foo
}
`
class NetworkError extends Error {
bodyText
statusCode
result
message
response
constructor(networkErrorProps, ...params) {
super(...params)
const {
name,
bodyText,
statusCode,
result,
message,
response,
} = networkErrorProps
this.name = name
this.bodyText = bodyText
this.statusCode = statusCode
this.result = result
this.message = message
this.response = response
}
}
describe('errorLink', () => {
it('should handle error and send notification', async () => {
const mockLink = new ApolloLink((operation, forward) => {
let fetchResult: FetchResult = {
errors: [], // put GraphQLErrors here
data: null,
}
// Thanks https://stackoverflow.com/a/70936974/21217
let linkResult = Observable.of(fetchResult).map(_ => {
throw new NetworkError({
name: 'ServerParseError',
message: 'Unexpected token',
response: {},
bodyText: '<!DOCTYPE html><html><head></head><body>Error</body></html>',
statusCode: 503,
result: {},
})
})
return linkResult
})
async function executeLink<T = any, U = any>(
dataLink: ApolloLink
) {
const linkResult = {} as LinkResult<T>
return new Promise<LinkResult<T>>((resolve, reject) => {
execute(from([errorLink, dataLink]), {
query: MockQuery,
}).subscribe(
result => {
// We don't care
},
error => {
// We can resolve here to skip having a try / catch around the await below
resolve(linkResult)
},
)
})
}
const notificationSpy = jest.spyOn(notificationService, 'notify')
await executeLink(mockLink)
expect(notificationSpy).toHaveBeenCalledWith('An Error Occurred')
})
})
When I submit my form, it triggers an action login (from userActions). In this action, I use dispatch to use my userService which makes an API call.
When I submit it, the dispatch is not working. If I console.log the result of the action I have my code that appears, like this:
Action was called // Custom message
dispatch => {
dispatch(request({
email
}))
_services_userService__WEBPACK_IMPORTED_MODULE_1__["userService"].login(email, password).then( appSate => {return appSate;},error => {console.lo…
I am supposed to retrieve my user... What is wrong here ?
LoginForm.js
handleFormSubmit(e) {
e.preventDefault();
const credentials = {
email: this.state.email,
password: this.state.password
}
if (credentials) {
let test = login(credentials);
console.log("Action was called");
console.log(test);
this.setState(redirect => true)
}
}
userActions.js -> login()
export const login = (email,password) => {
console.log('is in action');
return dispatch => {
dispatch(request({ email }));
userService.login(email,password)
.then(
appSate => {
return appSate;
},
error => {
console.log(error);
}
);
};
function request(user) { return { type: userConstants.LOGIN_REQUEST,user } }
}
userService.js -> login()
function login(credentials) {
console.log("In userService login function");
return axios.post('/api/login',credentials)
.then(response => {
if (response.data.success) {
console.log("Login Successful!");
let userData = {
firstname: response.data.user.firstname,
surname: response.data.user.surname,
id: response.data.user.id,
email: response.data.user.email,
auth_token: response.data.access_token,
};
let appState = {
isLoggedIn: true,
user: userData
};
localStorage.setItem("appState",JSON.stringify(appState));
return appState;
}
});
}
I think you forgot return statement userActions.js. Try this
export const login = (email,password) => {
console.log('is in action');
return dispatch => {
dispatch(request({ email }));
return userService.login(email,password)
.then(
appSate => {
return appSate;
},
error => {
console.log(error);
}
);
};
function request(user) { return { type: userConstants.LOGIN_REQUEST,user } }
}