I have a file that imports another. I want to mock the other import differently in each test yet have it show through the file that imports it.
I have tried various mocking and importing approaches through googling but none have worked.
Consider the files:
settings.js
export default { mySetting: null };
store.js
import settings from "./settings";
export default {
settings: { ...settings },
};
settingsDemo.js
import store from "./store";
it("default settings", () => {
expect(store.settings.mySetting).toBe(null);
});
it("mocked to true", () => {
expect(store.settings.mySetting).toBe(true);
});
it("mocked to false", () => {
expect(store.settings.mySetting).toBe(false);
});
how do I mock these files within settingsDemo.js to have all 3 tests pass without editing settings.js or store.js?
Note:
this is similar to my previous question, but the solution to import the dependency and change the value doesn't work in this case since store doesn't reference the settings object here but instead clones it for an initial value.
The same principle applies: "while you can't change the value of imports, you can change the objects that they are referring to."
In this case store is an object that contains a nested settings object. While it is not possible to simply assign store to something else, it is possible to assign store.settings to something else. It is also possible to set store.settings.mySetting to something else:
import store from "./store";
it("default settings", () => {
expect(store.settings.mySetting).toBe(null); // SUCCESS
});
it("mocked to true", () => {
store.settings = { mySetting: true }; // set store.settings to something else
expect(store.settings.mySetting).toBe(true); // SUCCESS
});
it("mocked to false", () => {
store.settings.mySetting = false; // set store.settings.mySetting to something else
expect(store.settings.mySetting).toBe(false); // SUCCESS
});
I'm not sure if I understand your question. It sounds like you just want to mock a simple object
"{settings:{ mySetting: null }}".
let store = {settings:{ mySetting: null }}
it("default settings", () => {
store.settings.mySetting = null
expect(store.settings.mySetting).toBe(null);
});
it("mocked to true", () => {
store.settings.mySetting = true
expect(store.settings.mySetting).toBe(true);
});
it("mocked to false", () => {
store.settings.mySetting = false
expect(store.settings.mySetting).toBe(false);
});
Related
I am trying to mock the init method provided by sentry-expo and so far, this is what I have come up with:
setupFilesAfterEnv.ts
import '#testing-library/jest-native/extend-expect';
import * as Sentry from 'sentry-expo';
import sentryTestkitSuite from 'sentry-testkit';
const DUMMY_DSN = 'https://acacaeaccacacacabcaacdacdacadaca#sentry.io/000001';
const { sentryTransport } = sentryTestkitSuite();
// https://stackoverflow.com/questions/44649699/service-mocked-with-jest-causes-the-module-factory-of-jest-mock-is-not-allowe
// Cannot use the imported module as a value directly
const mockSentryTransport = sentryTransport as jest.Mocked<
typeof sentryTransport
>;
jest.mock('sentry-expo', () => ({
...jest.requireActual('sentry-expo'),
init: (options?: Sentry.SentryExpoNativeOptions) => ({
...options,
transport: mockSentryTransport,
}),
}));
beforeAll(() =>
Sentry.init({
dsn: DUMMY_DSN,
release: 'test',
tracesSampleRate: 1,
beforeSend(event) {
return {
...event,
extra: { os: 'mac-os' },
};
},
}),
);
beforeEach(() => {
sentryTestkitSuite().testkit.reset();
});
All the test cases which have used Sentry to capture exceptions successfully pass.
Now, I have created a file for adding standard crash-reporting utilities:
crash-reporting.ts
import * as Sentry from 'sentry-expo';
import { getEnvironmentConfig } from '#utils/environment/environment';
const routingInstrumentation =
new Sentry.Native.ReactNavigationInstrumentation();
export const initialiseCrashReporting = () => {
return Sentry.init({
dsn: getEnvironmentConfig()?.sentryDSN,
// Enable it only when you install the Expo development build on your device/simulator
// If you enable it while running the app in Expo Go, native dependencies will not work as expected such as Sentry
enableInExpoDevelopment: __DEV__,
debug: __DEV__, // If `true`, Sentry will try to print out useful debugging information if something goes wrong with sending the event. Set it to `false` in production,
environment: getEnvironmentConfig()?.appEnv ?? 'development',
tracesSampleRate: __DEV__ ? 1 : 0.2,
integrations: [
new Sentry.Native.ReactNativeTracing({
tracingOrigins: ['localhost', /^\//],
routingInstrumentation,
}),
],
});
};
export const { wrap: sentryWrap } = Sentry.Native;
I am trying to test the above crash-reporting module like so:
crash-reporting.test.ts
import * as Sentry from 'sentry-expo';
import { initialiseCrashReporting } from './crash-reporting';
jest.mock('sentry-expo', () => {
const originalModule = jest.requireActual('sentry-expo');
return {
...originalModule,
init: jest.fn(),
};
});
describe('Crash Reporting Test Suite', () => {
it('should initialise sentry', () => {
const initSpy = jest.spyOn(Sentry, 'init');
initialiseCrashReporting();
expect(initSpy).toHaveBeenCalled();
});
});
Even though initialiseCrashReporting gets called, spyOn never catches the event where init gets called.
I realised that the globally mocked sentry-expo never gets overridden with the one in the crash-reporting.test.ts file.
I have 2 below-given questions related to this problem:
How can I override the globally mocked modules? Or how can I be assured that by calling initialiseCrashReporting, I am initialising sentry?
Can we override global beforeall for specific test cases?
Thanks in anticipation!
I am currently write some tests for my angular application. When I mock class to check if method is correctly called, I make a mock implementation. When I do that my code is underlined with red dots because my mock does not respect the true implementation of the type.
Here is an example. Here I want mock HttpLink class in order to test that create function is called. I dont mind how HttpLink object is construct. So I mocked HttpLink class and I mock create function. But visual studio code put red dot when I call the constructor because it does not respect the true HttpLink implementation :
import { Apollo } from 'apollo-angular'
jest.mock('apollo-angular', () => {
return {
Apollo: () => {
return {
create: jest.fn()
}
}
}
})
import { HttpLink } from 'apollo-angular-link-http'
jest.mock('apollo-angular-link-http', () => {
return {
HttpLink: () => {
return {
create: jest.fn()
}
}
}
})
import { GraphqlService } from './graphql.service'
describe('GraphqlService', () => {
let apollo = new Apollo()
let httpLink = new HttpLink() // <====== This is red because type checking see this class need one argument but I mocked the constructor.
let graphqlService
beforeAll(() => {
graphqlService = new GraphqlService(apollo, httpLink)
})
it('should be created', () => {
expect(graphqlService).toBeTruthy()
})
it('should create apollo client correctly', () => {
expect(apollo.create).toHaveBeenCalled()
})
})
Is there a way to desactivate type checking that visual studio code does, but only for test files ?
You can go to the preferences and then Settings
In User Settings, you have to update following :
"typescript.validate.enable": true,
to
"typescript.validate.enable": false,
Hope it will help you !!
[EDIT - POSSIBLE SOLUTION]
So I realised that my componentWillMount is an async method since it is using an async fs wrapper to do fs operations. So I made the beforeEach function argument async and awaited on the Enzyme.shallow. This seems to have worked. It just came to me that if it's async maybe the lifecycle hadn't run yet when the expectation was ran... What do you think?
It now looks like this
// root/Meetings/__tests__/MeetingsScreen.test.js
...
import sortMeetings from '../../helpers/sort';
jest.mock('../../helpers/sort', () => jest.fn());
describe('MeetingsScreen', () => {
let wrapper;
const mockValueForMeetings = [];
sortMeetings.mockReturnValue(mockValueForMeetings);
beforeEach(async () => {
wrapper = await Enzyme.shallow(<MeetingsScreen />);
});
it('should call the sort method', () => {
expect(sortMeetings).toHaveBeenCalled();
});
});
[ORIGINAL QUESTION]
I am mocking an imported function and the test says that it wasn't called but it returns the stubbed value.
I have this class/screen in react-native that imports a helper file that only has one function to do a sort.
// root/helpers/sort.js
import moment from 'moment';
const compareDateTime = (a, b) => {
...
};
const sortMeetings = meetings => meetings.sort(compareDateTime);
export default sortMeetings;
My class looks like this
// root/Meetings/MeetingsScreen.js
...
import sortMeetings from '../helpers/sort';
export default class MeetingsScreen extends Component {
...
componentDidMount() {
this.updateState();
}
updateState = async () => {
const meetingsOnFile = await fsStorage.getItem('meetings'); // this is also stubbed and returns an [{}]
const meetings = sortMeetings(meetingsOnFile);
this.setState({ meetings });
}
render() {
return (
<MeetingList meetings={this.state.meetings} />
);
}
}
And this it my test. I am using Jest.
// root/Meetings/__tests__/MeetingsScreen.test.js
...
import sortMeetings from '../../helpers/sort';
jest.mock('../../helpers/sort', () => jest.fn());
describe('MeetingsScreen', () => {
let wrapper;
const mockValueForMeetings = [];
sortMeetings.mockReturnValue(mockValueForMeetings);
beforeEach(() => {
wrapper = Enzyme.shallow(<MeetingsScreen />);
});
it('should call the sort method', () => {
expect(sortMeetings).toHaveBeenCalled();
});
});
So If I got it right, since the import of the default function returns a function when using Jest i am mocking with a function. Before the test I am setting that mock to always return an empty array and I am printing out down the code after the call of the function (in the source code) and it indeed returns an array. I changed it to others values as well (i.e. 13, [2, 3, 4], et.c) and they all get returned. So I would assume that the mock function gets called. But the expectation fails. If I print the sortMeetings.mock as well, it just shows empty arrays in it's values {"calls":[],"instances":[],"timestamps":[]}
Could someone point to the mistake I am doing. I think I might be wrong about how import default functions work or how Jest is doing the mocking
Jest provides a way to mock functions as described in their docs
apiGetMethod = jest.fn().mockImplementation(
new Promise((resolve, reject) => {
const userID = parseInt(url.substr('/users/'.length), 10);
process.nextTick(
() => users[userID] ? resolve(users[userID]) : reject({
error: 'User with ' + userID + ' not found.',
});
);
});
);
However these mocks only seem to work when the function is called directly in a test.
describe('example test', () => {
it('uses the mocked function', () => {
apiGetMethod().then(...);
});
});
If I have a React Component defined as such how can I mock it?
import { apiGetMethod } from './api';
class Foo extends React.Component {
state = {
data: []
}
makeRequest = () => {
apiGetMethod().then(result => {
this.setState({data: result});
});
};
componentDidMount() {
this.makeRequest();
}
render() {
return (
<ul>
{ this.state.data.map((data) => <li>{data}</li>) }
</ul>
)
}
}
I have no idea how to make it so Foo component calls my mocked apiGetMethod() implementation so that I can test that it renders properly with data.
(this is a simplified, contrived example for the sake of understanding how to mock functions called inside react components)
edit: api.js file for clarity
// api.js
import 'whatwg-fetch';
export function apiGetMethod() {
return fetch(url, {...});
}
You have to mock the ./api module like this and import it so you can set the implemenation of the mock
import { apiGetMethod } from './api'
jest.mock('./api', () => ({ apiGetMethod: jest.fn() }))
in your test can set how the mock should work using mockImplementation:
apiGetMethod.mockImplementation(() => Promise.resolve('test1234'))
In case the jest.mock method from #Andreas's answer did not work for you. you could try the following in your test file.
const api = require('./api');
api.apiGetMethod = jest.fn(/* Add custom implementation here.*/);
This should execute your mocked version of the apiGetMethod inside you Foo component.
Here is an updated solution for anyone struggling with this in '21. This solution uses Typescript, so be aware of that. For regular JS just take out the type calls wherever you see them.
You import the function inside your test at the top
import functionToMock from '../api'
Then you indeed mock the call to the folder outside of the tests, to indicate that anything being called from this folder should and will be mocked
[imports are up here]
jest.mock('../api');
[tests are down here]
Next we mock the actual function we're importing. Personally I did this inside the test, but I assume it works just as well outside the test or inside a beforeEach
(functionToMock as jest.Mock).mockResolvedValue(data_that_is_returned);
Now here's the kicker and where everyone seems to get stuck. So far this is correct, but we are missing one important bit when mocking functions inside a component: act. You can read more on it here but essentially we want to wrap our render inside this act. React testing library has it's own version of act. It is also asynchronous, so you have to make sure your test is async and also define the destructured variables from render outside of it.
In the end your test file should look something like this:
import { render, act } from '#testing-library/react';
import UserGrid from '../components/Users/UserGrid';
import { data2 } from '../__fixtures__/data';
import functionToMock from '../api';
jest.mock('../api');
describe("Test Suite", () => {
it('Renders', async () => {
(functionToMock as jest.Mock).mockResolvedValue(data2);
let getAllByTestId: any;
let getByTestId: any;
await act(async () => {
({ getByTestId, getAllByTestId } = render(<UserGrid />));
});
const container = getByTestId('grid-container');
const userBoxes = getAllByTestId('user-box');
});
});
Another solution to mock this would be:
window['getData'] = jest.fn();
I'm using VueJS from Vue CLI. So all my components are in .vue format.
In one of my components, I have an array called fields in the data section.
//Component.vue
data() {
return {
fields : [{"name" : "foo", "title" : "Foosteria"}, {"name" : "bar", "title" : "Barrista"}]
}
}
I have a computed property that is a subset of fields
//Component.vue
computed : {
subsetOfFields () {
// Something else in component data determines this list
}
}
I've set up all of my unit tests in jasmine like this and they work fine.
//Component.spec.js
import Vue from 'vue'
import MyComponent from 'Component.vue'
describe("Component test", function() {
var myComponentVar = new Vue(MyComponent);
var vm = myComponentVar.$mount();
beforeEach(function() {
vm = myComponentVar.$mount();
);
afterEach(function() {
vm = myComponentVar.$destroy();
});
it("First spec tests something", function() {
...
});
});
For everything else, doing something inside the spec, then running assertions on the data objects works just fine. However, running an assertion on subsetOfFields always returns an empty array. Why so? What should I do, in order to be able to test it?
FYI, I even tried nesting the spec inside another describe block and then adding a beforeEach which initializes the fields array. It did not work.
However, initializing fields inside the generic beforeEach function worked. But I don't want to initialize the fields array with that mock data for the other specs.
I came across this link that talks about testing and the section you'll need to look at is the Vue.nextTick(...) section
https://alligator.io/vuejs/unit-testing-karma-mocha/
The block I'm talking about is below:
import Vue from 'vue';
// The path is relative to the project root.
import TestMe2 from 'src/components/TestMe2';
describe('TestMe2.vue', () => {
...
it(`should update when dataText is changed.`, done => {
const Constructor = Vue.extend(TestMe2);
const comp = new Constructor().$mount();
comp.dataProp = 'New Text';
Vue.nextTick(() => {
expect(comp.$el.textContent)
.to.equal('New Text');
// Since we're doing this asynchronously, we need to call done() to tell Mocha that we've finished the test.
done();
});
});
});