axios / jest - unabled to perform a call request (TypeError: Cannot read property 'then' of undefined) - axios

I'm struggling to perform a test with jest concerning an axios api call
here is my API call, that works perfectly within my program
import axios from 'axios';
import crypto from 'crypto';
import { prop } from 'ramda';
const baseUrl = 'http://gateway.marvel.com:80';
const uri = '/v1/public/characters';
const charactersUrl = baseUrl + uri;
const timestamp = [Math.round(+new Date() / 1000)];
const privateApi = 'XXX';
const publicApi = 'XXX';
const concatenatedString = timestamp.concat(privateApi, publicApi).join('');
const hash = crypto.createHash('md5').update(`${concatenatedString}`).digest('hex');
const charactersApi = () =>
axios
.get(charactersUrl, {
params: {
ts: timestamp,
apikey: publicApi,
hash,
},
})
.then(prop('data'));
export default charactersApi;
When I'm trying to test it, that way:
import axiosMock from 'axios';
import charactersApi from '../marvelApi';
jest.mock('axios', () => ({
get: jest.fn(),
}));
describe('tools | marvelApi', () => {
const piece = { name: '3D-MAN' };
axiosMock.get.mockResolvedValueOnce({ data: piece });
it('should get the character', () => {
return charactersApi().then(elem => {
expect(elem.name).toEqual('3D-MAN');
});
});
});
I get the following message from jest
TypeError: Cannot read property 'then' of undefined
16 |
17 | const charactersApi = () =>
> 18 | axios
| ^
19 | .get(charactersUrl, {
20 | params: {
21 | ts: timestamp,
at charactersApi (src/tools/marvelApi.js:18:3)
at Object.<anonymous> (src/tools/tests/marvelApi.test.js:13:12)
What I have tried
A common error is to forget the return statement within the function that contain the request API, in my case it's done correctly (first piece of code -> charactersApi()) source1, source2
I also tried to return a Promise from the mocked Axios as I have seen on another SO ticket
jest.mock('axios', () => ({
get: jest.fn(() => Promise.resolve()),
}));
I think my axios mock is not correct, because the struggle comes from the test while the production version work well
Any thoughts ?

You can spy on the "axios.get" calls and resolve them to a fixed (mocked) value:
/**
* #jest-environment jsdom
*/
const axios = require('axios')
beforeAll(() => {
jest.spyOn(axios, 'get').mockImplementation()
})
afterAll(() => {
jest.restoreAllMocks()
})
it('returns the mocked response', async () => {
axios.get.mockResolvedValue({ data: 'foo' })
const res = await axios.get('https://api.github.com')
expect(res).toEqual({ data: 'foo' })
})
You shouldn't use jest.mock because it mocks a module that your imported code may be using. As far as I know, it doesn't affect the current module's imports (and you import axios as a part of your test).
Recommended solution
I strongly discourage you from spying/mocking axios directly. See my argumentation below.
You're mocking implementation details of axios. In other words, you take the axios.get function and throw it away, alongside any internal logic it may have, and replace it with a hard mock. This means your test no longer uses axios, instead it uses an emptied mocked shell of axios. This makes your test different from your actual code, which, in turn, decreases the confidence such a test gives you.
You're coupling your mocks with a specific request client (axios). Such an approach is not a long-term investment, as you're writing axios-specific mocks. You can't reuse such mocks for requests made by other clients (i.e. window.fetch, Apollo, etc.), because they have their own implementation details (i.e. window.fetch has no .get() to spy on), which only encourages you to write more implementation-specific logic in tests.
You can learn more about the disadvantages of direct mocking of request clients in the Stop mocking fetch article by Kent C. Dodds. It uses window.fetch mocks as an example, but you may replace it with ANY_REQUEST_CLIENT when reading.
I highly recommend using tools like Mock Service Worker (MSW) that will encourage you to write abstracted mocks that don't rely on any request clients (you can use them no matter how your tested code makes a request) and can even be reused across different testing levels (the same mocks for Jest, Storybook, or Cypress).
Here's how your test would look like with MSW:
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import charactersApi from '../marvelApi';
const server = setupServer(
rest.get('http://gateway.marvel.com:80/v1/public/characters', (req, res, ctx) => {
return res(ctx.json({
data: {
name: '3D-MAN'
}
}))
})
)
beforeAll(() => server.listen()
afterAll(() => server.close())
describe('tools | marvelApi', () => {
it('should get the character', () => {
return charactersApi().then(elem => {
expect(elem.name).toEqual('3D-MAN')
})
})
})
Notice how there are no details about how the request is made, only which request to intercept and mock its response.
You can follow a detailed tutorial on how to Get started with MSW. There's also a great video on API mocking and what problems MSW solves.

Related

Mocking authentication when testing MSAL React Apps

Our app is wrapped in the MSAL Authentication Template from #azure/msal-react in a standard way - key code segments are summarized below.
We would like to test app's individual components using react testing library (or something similar). Of course, when a React component such as SampleComponentUnderTest is to be properly rendered by a test as is shown in the simple test below, it must be wrapped in an MSAL component as well.
Is there a proper way to mock the MSAL authentication process for such purposes? Anyway to wrap a component under test in MSAL and directly provide test user's credentials to this component under test? Any references to useful documentation, blog posts, video, etc. to point us in the right direction would be greatly appreciated.
A Simple test
test('first test', () => {
const { getByText } = render(<SampleComponentUnderTest />);
const someText = getByText('A line of text');
expect(someText).toBeInTheDocument();
});
Config
export const msalConfig: Configuration = {
auth: {
clientId: `${process.env.REACT_APP_CLIENT_ID}`,
authority: `https://login.microsoftonline.com/${process.env.REACT_APP_TENANT_ID}`,
redirectUri:
process.env.NODE_ENV === 'development'
? 'http://localhost:3000/'
: process.env.REACT_APP_DEPLOY_URL,
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: false,
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) {
return;
}
switch (level) {
case LogLevel.Error:
console.error(message);
return;
case LogLevel.Info:
console.info(message);
return;
case LogLevel.Verbose:
console.debug(message);
return;
case LogLevel.Warning:
console.warn(message);
return;
default:
console.error(message);
}
},
},
},
};
Main app component
const msalInstance = new PublicClientApplication(msalConfig);
<MsalProvider instance={msalInstance}>
{!isAuthenticated && <UnauthenticatedHomePage />}
{isAuthenticated && <Protected />}
</MsalProvider>
Unauthenticated component
const signInClickHandler = (instance: IPublicClientApplication) => {
instance.loginRedirect(loginRequest).catch((e) => {
console.log(e);
});
};
<UnauthenticatedTemplate>
<Button onClick={() => signInClickHandler(instance)}>Sign in</Button>
</UnauthenticatedTemplate>
Protected component
<MsalAuthenticationTemplate
interactionType={InteractionType.Redirect}
errorComponent={ErrorComponent}
loadingComponent={LoadingComponent}
>
<SampleComponentUnderTest />
</MsalAuthenticationTemplate>
I had the same issue as you regarding component's test under msal-react.
It took me a couple of days to figure out how to implement a correct auth mock.
That's why I've created a package you will find here, that encapsulates all the boilerplate code : https://github.com/Mimetis/msal-react-tester
Basically, you can do multiple scenaris (user is already logged, user is not logged, user must log in etc ...) in a couple of lines, without having to configure anything and of course without having to reach Azure AD in any cases:
describe('Home page', () => {
let msalTester: MsalReactTester;
beforeEach(() => {
// new instance of msal tester for each test
msalTester = new MsalReactTester();
// spy all required msal things
msalTester.spyMsal();
});
afterEach(() => {
msalTester.resetSpyMsal();
});
test('Home page render correctly when user is logged in', async () => {
msalTester.isLogged();
render(
<MsalProvider instance={msalTester.client}>
<MemoryRouter>
<Layout>
<HomePage />
</Layout>
</MemoryRouter>
</MsalProvider>,
);
await msalTester.waitForRedirect();
let allLoggedInButtons = await screen.findAllByRole('button', { name: `${msalTester.activeAccount.name}` });
expect(allLoggedInButtons).toHaveLength(2);
});
test('Home page render correctly when user logs in using redirect', async () => {
msalTester.isNotLogged();
render(
<MsalProvider instance={msalTester.client}>
<MemoryRouter>
<Layout>
<HomePage />
</Layout>
</MemoryRouter>
</MsalProvider>,
);
await msalTester.waitForRedirect();
let signin = screen.getByRole('button', { name: 'Sign In - Redirect' });
userEvent.click(signin);
await msalTester.waitForLogin();
let allLoggedInButtons = await screen.findAllByRole('button', { name: `${msalTester.activeAccount.name}` });
expect(allLoggedInButtons).toHaveLength(2);
});
I am also curious about this, but from a slightly different perspective. I am trying to avoid littering the code base with components directly from msal in case we want to swap out identity providers at some point. The primary way to do this is to use a hook as an abstraction layer such as exposing isAuthenticated through that hook rather than the msal component library itself.
The useAuth hook would use the MSAL package directly. For the wrapper component however, I think we have to just create a separate component that either returns the MsalProvider OR a mocked auth provider of your choice. Since MsalProvider uses useContext beneath the hood I don't think you need to wrap it in another context provider.
Hope these ideas help while you are thinking through ways to do this. Know this isn't a direct answer to your question.

React Testing Library for Actions in redux

I am new to React Testing Library and have issue with actions.
Can anyone please guide me
I have tried below code and its giving error Received: [Function anonymous]
export const openText = () => (dispatch: Dispatch) => {
dispatch({
type: actionTypes.Text,
payload: true
});
};
Test Case
it('open text() => {
const expectedAction = {
type: actionTypes.OPEN_Text,
payload:'value
};
const action = actions.openText('value);
expect(action).toEqual(expectedAction);
});
Error: It says Received: [Function anonymous]
React Testing Library, as the name implies, is used for testing React components. You do not need it in this case.
You seem to be trying to test a Redux action creator. I suggest you follow the async action creators section from the Redux docs.
Your test currently fails because you are expecting to receive an object but your action creator returns a function.

Does testcafe support testing of rest api

Tests hang in the testcafe browser when you try to test a rest api url directly.
I am trying to run a test against my rest API endpoint using request hooks, but when I run the test from the command line, the browser opens the API endpoint and loads it and hangs. The test doesn't pass or fail and hangs. The rest API endpoint just returns a JSON response.
const logger = RequestLogger('https://example.com/search/suggestions?search=testkeyword');
fixture `test`
.page('https://example.com/search/suggestions?search=testkeyword');
test
.requestHooks(logger)
('test', async t => {
// Ensure that the response has been received and that its status code is 200.
await t.expect(logger.contains(record => record.response.statusCode === 200)).ok();
const logRecord = logger.requests[0];
console.log(logRecord.userAgent);
console.log(logRecord.request.url);
console.log(logRecord.request.method);
console.log(logRecord.response.statusCode);
});
I expect the test to pass checking for 200 status code, but the test hangs without showing pass/fail. Does testcafe support testing of rest API endpoints? I have checked this issue - https://github.com/DevExpress/testcafe/issues/1471 where it says testcafe doesn't support non-html pages. Please confirm.
You are right, TestCafe is intended to work with html pages, but it will use the "about:blank" page if you don't define any url. You can use the regular node.js HTTP API without request hooks for this case. Look at the following example:
import http from 'http';
const getResponseData = (url) => new Promise((resolve, reject) => {
http.get(url, res => {
const { statusCode } = res;
const contentType = res.headers['content-type'];
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => resolve({ statusCode, contentType, rawData }));
}).on('error', e => reject(e));
});
fixture `Test REST API`;
test('Simple Test', async t => {
const response = await getResponseData('http://example.com/api/1');
await t
.expect(response.statusCode).eql(200);
});
TestCafe 1.20.0+ offers the t.request method. You can use it for REST API testing. In other words, you can incorporate API testing right into your existing functional TestCafe tests. You no longer need to use any third-party libraries.
You can read about the API testing feature in the corresponding guide.

Unable to get Moxios stubRequest to work

I'm having issues getting stubRequest to work properly. Here's my code:
it('should stub my request', (done) => {
moxios.stubRequest('/authenticate', {
status: 200
})
//here a call to /authenticate is being made
SessionService.login('foo', 'bar')
moxios.wait(() => {
expect(something).toHaveHappened()
done()
})
})
This works fine:
it('should stub my request', (done) => {
SessionService.login('foo', 'bar')
moxios.wait(async () => {
let request = moxios.requests.mostRecent()
await request.respondWith({
status: 200
})
expect(something).toHaveHappened()
done()
})
})
The second method just get's the last call though, and I'd really like to be able to explicitely stub certain requests.
I'm running Jest with Vue.
I landed here with a similar goal and eventually solved it using a different approach that may be helpful to others:
moxios.requests has a method .get() (source code) that lets you grab a specific request from moxios.requests based on the url. This way, if you have multiple requests, your tests don't require the requests to occur in a specific order to work.
Here's what it looks like:
moxios.wait(() => {
// Grab a specific API request based on the URL
const request = moxios.requests.get('get', 'endpoint/to/stub');
// Stub the response with whatever you would like
request.respondWith(yourStubbedResponseHere)
.then(() => {
// Your assertions go here
done();
});
});
NOTE:
The name of the method .get() is a bit misleading. It can handle different types of HTTP requests. The type is passed as the first parameter like: moxios.requests.get(requestType, url)
it would be nice if you show us the service. Service call must be inside the moxios wait func and outside must be the axios call alone. I have pasted a simplified with stubRequest
describe('Fetch a product action', () => {
let onFulfilled;
let onRejected;
beforeEach(() => {
moxios.install();
store = mockStore({});
onFulfilled = sinon.spy();
onRejected = sinon.spy();
});
afterEach(() => {
moxios.uninstall();
});
it('can fetch the product successfully', done => {
const API_URL = `http://localhost:3000/products/`;
moxios.stubRequest(API_URL, {
status: 200,
response: mockDataSingleProduct
});
axios.get(API_URL, mockDataSingleProduct).then(onFulfilled);
const expectedActions = [
{
type: ACTION.FETCH_PRODUCT,
payload: mockDataSingleProduct
}
];
moxios.wait(function() {
const response = onFulfilled.getCall(0).args[0];
expect(onFulfilled.calledOnce).toBe(true);
expect(response.status).toBe(200);
expect(response.data).toEqual(mockDataSingleProduct);
return store.dispatch(fetchProduct(mockDataSingleProduct.id))
.then(() => {
var actions = store.getActions();
expect(actions.length).toBe(1);
expect(actions[0].type).toBe(ACTION.FETCH_PRODUCT);
expect(actions[0].payload).not.toBe(null || undefined);
expect(actions[0].payload).toEqual(mockDataSingleProduct);
expect(actions).toEqual(expectedActions);
done();
});
});
});
})

Write test axios-mock-adapter with axios.create()

I want to test my http service but get error.
So, my test file
api.js
import axios from 'axios';
export const api = axios.create();
fetchUsers.js
import api from './api';
export const fetchUsers = (params) api.get('/api/users', { params })
.then(({data}) => data)
fetchUsers.spec.js
import MockAdapter from 'axios-mock-adapter'
import api from './api';
const mock = new MockAdapter(api);
describe('fetchUsers', () => {
it('should send request', (done) => {
const data = { data: ['user'] };
mock.onGet('/api/users').reply(200, data);
fetchUsers().then((response) => {
expect(response).toEqual(data.data);
done();
});
});
});
But I get error here
Error: connect ECONNREFUSED 127.0.0.1:80
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1158:14)
If I replace in api.js axios.create() with axios its' working. But how to test with created axios instance? I'll need to ass there parameters when create it.
Anyone can help with that?
Hi I had the same issue and had to answer myself here https://stackoverflow.com/a/51414152/73323
Here is the gist:
First off, you don't need the axios-mock-adapter library.
Create a mock for axios in src/__mocks__:
// src/__mocks__/axios.ts
const mockAxios = jest.genMockFromModule('axios')
// this is the key to fix the axios.create() undefined error!
mockAxios.create = jest.fn(() => mockAxios)
export default mockAxios
Then in your test file, the gist would look like:
import mockAxios from 'axios'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
// for some reason i need this to fix reducer keys undefined errors..
jest.mock('../../store/rootStore.ts')
// you need the 'async'!
test('Retrieve transaction data based on a date range', async () => {
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore()
const mockData = {
'data': 123
}
/**
* SETUP
* This is where you override the 'post' method of your mocked axios and return
* mocked data in an appropriate data structure-- {data: YOUR_DATA} -- which
* mirrors the actual API call, in this case, the 'reportGet'
*/
mockAxios.post.mockImplementationOnce(() =>
Promise.resolve({ data: mockData }),
)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
// work
await store.dispatch(reportGet())
// assertions / expects
expect(store.getActions()).toEqual(expectedActions)
expect(mockAxios.post).toHaveBeenCalledTimes(1)
})