I'm trying to test a hook in isolation with #testing-library/render-hooks in a React Native project. The hook I have looks something like this:
const useStatus = () => {
const [ status, setStatus ] = useState(null)
const { data: data1 } = useDataHook1()
const { data: data2 } = useDataHook2()
useEffect(() => {
if (data1 && data2) {
// Some logic to determine status based on data1 + data2
setStatus(...)
}
}, [data1, data2])
return { status }
}
So I want to test useStatus and I'd like to mock useData1 and useData2, all three hooks of which are defined in the same file. My test looks like so:
import * as hooks from 'path/to/hooks'
const data1Spy = jest.spyOn(hooks, 'useData1')
const data2Spy = jest.spyOn(hooks, 'useData2')
describe('useStatus', () => {
it('should render', async () => {
data1Spy.mockReturnValue({ data: 'foo' }
data2Spy.mockReturnValue({ data: 'bar' }
const { result, waitForNextUpdate } = renderHook(() => hooks.useStatus())
await waitForNextUpdate()
console.log(result.current.status)
}
}
The console logs values that I would expect if the nested data hooks weren't returning, and the error:
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.
appears, which tells me that the mocks aren't being set correctly. (Thinking the SWR queries are trying to dispatch in a test environment, leading to unresolved promises floating around after the test finishes.)
Some things I've tried:
Using mockReturnValue as seen in the above code
Using mockImplementation instead of mockReturnValue
Using jest.mock + jest.requireActual pattern as is done here
I'm not sure what else to try. I've been able to mock hooks before using the above pattern when rendering a component, but this is my first time working with rendering the hook directly. Any thoughts?
Related
I'm new to jest and trying to test my React Native app. I have what seems like a very simple case: a function called getPastWeek in my AppProvider which I want to test by passing it an argument then checking the return value. However, I am struggling to import the getPastWeek function into my test file:
context.js
const AppProvider = ({children}) => {
const getPastWeek = (param) => {
return param + 5;
}
return (<AppContext.Provider value={{getPastWeek}}>{children}</AppContext.Provider>)
}
export const useGlobalContext = () => useContext(AppContext)
export {AppProvider, AppContext}
context.test.tsx
const { getPastWeek } = useGlobalContext();
it("Test description", () => {
expect(
getPastWeek([{ data: () => ({ date: new Date() }) }]).toBe(true)
);
});
However, this does not work since useGlobalContext needs to be called from within a AppProvider. How can I achieve this desired behaviour and test the getPastWeek function?
EDIT: After doing some digging it seems like one way to achieve this would be to create a test component wrapped in the AppProvider in the test file. However, this seems like overkill to test a single function. Is there a simpler way to do this?
I guess I could also export the function but it seems a little messy to do so
I have created a custom hook that returns the translated value using the useTranslation hook.
import { useTranslation } from "next-i18next";
export const useCustomHook = (data) => {
const {t, i18n: { language: locale }} = useTranslation();
const value = {
field: t("some.key.from.json.file", { arg: data.arg }),
field2: data.name,
field3: t("another.key", {
arg: data.arg2, count: 3
})
}
return value;
};
I want to create a unit test for this custom hook, but I can't get the useTranslation hook to work as it does when running the app itself. Further info my current setup is as follows:
1- I'm using Nextjs with next-i18next library.
2- No i18n provider to wrap the app, only using HOC from next-i18next to wrap _app.
3- I have 2 json files for locales.
Is there a way to allow the useTranslation hook to work and get the parsed value from the translation file? here's what I tried so far too:
1- mocking the useTranslation hook, but this returns the ("another.key") as is without the parsed value.
2- I tried to create a wrapper with i18n-next provider, but that didn't work too.
Here's my test file.
describe("useCustomHook()", () => {
it("Should return correctly mapped props", () => {
const { result } = renderHook(() =>
useCustomHook(mockData)
);
const data = result.current[0];
expect(data.field).toBe(mockData.field); // this returns ("some.key.from.json.file") it doesn't use the t function,
// ... //
});
I joined a big/medium project, I am having a hard time creating my first redux-saga-action things, it is going to be a lot of code since they are creating a lot of files to make things readable.
So I call my action in my componentDidMount, the action is being called because I have the alert :
export const fetchDataRequest = () => {
alert("actions data");
return ({
type: FETCH_DATA_REQUEST
})
};
export const fetchDataSuccess = data => ({
type: FETCH_DATA_SUCCESS,
payload: {
data,
},
});
This is my history saga : ( when I call the action with this type, The function get executed )
export default function* dataSaga() {
// their takeEverymethods
yield takeEvery(FETCH_DATA_REQUEST, fetchData);
}
This is what has to be called : ( I am trying to fill my state with data in a json file : mock )
export default function* fetchTronconsOfCircuit() {
try {
// Cal to api
const client = yield call(RedClient);
const data = yield call(client.fetchSomething);
// mock
const history = data === "" ? "" : fakeDataFromMock;
console.log("history : ");
console.log(history);
if (isNilOrEmpty(history)) return null;
yield put(fetchDataSuccess({ data: history }));
} catch (e) {
yield put(addErr(e));
}
}
And this is my root root saga :
export default function* sagas() {
// many other spawn(somethingSaga);
yield spawn(historySaga);
}
and here is the reducer :
const fetchDataSuccess = curry(({ data }, state) => ({
...state,
myData: data,
}));
const HistoryReducer = createSwitchReducer(initialState, [
[FETCH_DATA_SUCCESS, fetchDataSuccess],
]);
The method createSwitchReducer is a method created by the team to create easily a reducer instead of creating a switch and passing the action.type in params etc, their method is working fine, and I did exactly what they do for others.
Am I missing something ?
I feel like I did everything right but the saga is not called, which means it is trivial problem, the connection between action and saga is a common problem I just could not figure where is my problem.
I do not see the console.log message in the console, I added an alert before the try-catch but got nothing too, but alert inside action is being called.
Any help would be really really appreciated.
yield takeEvery(FETCH_DATA_REQUEST, fetchData);
should be
yield takeEvery(FETCH_DATA_REQUEST, fetchTronconsOfCircuit);
I would to try call a function already mocked. I use vueJS for the frond and Jest as unit test. Below a example of my code. My purpose is to test the call of « anotherFunction". The first test is succeed , not the second.Thanks for help or suggestion
code vueJS:
mounted() {
this.myfunction();
}
methods: {
myfunction() {
this.anotherFunction();
}
}
Jest code:
describe('Home.vue', () => {
let wrapper = null;
const options = {
mocks: {
$t: () => 'some specific text',
},
methods: {
myFunction: jest.fn(),
},
};
it('Should renders Home Component', () => {
// Given
wrapper = shallowMount(Home, options);
// Then
expect(wrapper).toBeTruthy();
});
it('Should call anotherFunction', async (done) => {
// Given
wrapper.vm.anotherFunction = jest.fn().mockResolvedValue([]);
// When
await wrapper.vm.myFunction();
// THIS THE PROBLEM, myFunction is mocked and I can't call the function 'anotherFunction' inside...
// Then
// expect(wrapper.vm.anotherFunction).toHaveBeenCalled();
});
});
I was finding a good way to help you if this test case. So, I thought in something like the chuck code below:
import { mount } from '#vue/test-utils';
describe('Home', () => {
it('method calls test case', () => {
const anotherMethodMock = jest.fn();
wrapper = mount(Home, {
methods: {
anotherMethod: anotherMethodMock
}
});
expect(anotherMethodMock).toHaveBeenCalled();
});
});
But, the Jest threw the following exception:
[vue-test-utils]: overwriting methods via the methods property is deprecated and will be removed in the next major version. There is no clear migration path for themethods property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex m ethod extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
I had the following insight, maybe, in this case, should be better to test the side effect of this anotherMethod calling. What does it change? Is something being shown to the user?
I believe that here we have started from the wrong concept.
I hope that this tip could be useful :)
As suggested by #Vinícius Alonso, We should avoid using methods and setMethods in our test cases because of it's deprecation. But you can still test the mounted lifecycle by mocking the functions that are being called during mount. So you can do something similar to below snippet.
describe('Mounted Lifecycle', () => {
const mockMethodOne = jest.spyOn(MyComponent.methods, 'methodOne');
const mockMethodTwo = jest.spyOn(MyComponent.methods, 'methodTwo');
it('Validate data and function call during mount', () => {
const wrapper = shallowMount(MyComponent);
expect(mockMethodOne).toHaveBeenCalled();
expect(mockMethodTwo).toHaveBeenCalled();
})
})
Do mount/shallowMount inside it only rather putting it outside of it as it was not working in my case. You can checkout more details on it if you want.
I'm writing unit tests for an Angular2 app in which a component is calling an asynchronous service when it is initialized in order to load its data. Before loading data, it should set a loading flag to true in order to show a spinner, and then the loading flag is set back to falseonce the data has been retrieved.
ngOnInit() {
this.reloadEmployees(this.filter);
}
reloadEmployees(filter: string) {
this.loading = true;
this.employeeService.getEmployees(filter).subscribe((results: Employee[]) => {
this.employees = results;
this.loading = false;
});
}
Here is how I wrote my test:
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [EmployeeComponent],
imports: [FormsModule, SpinnerModule, ModalModule, TranslateModule.forRoot()],
providers: [
{ provide: EmployeeService, useValue: employeeServiceStub },
]
});
fixture = TestBed.createComponent(EmployeeComponent);
component = fixture.componentInstance;
let employeeService = fixture.debugElement.injector.get(EmployeeService);
spy = spyOn(employeeService, 'getEmployees').and.returnValue(Observable.of(testEmployees));
});
it('should start loading employees when the component is initialized', fakeAsync(() => {
fixture.detectChanges();
expect(component.loading).toEqual(true);
}));
I was expecting the callback from the service to be run only if I call tick() in my test but apparently it is called anyway because component.loadingis already back to false when I check its value. Note that if I comment out the line that sets loading back to false in the callback of the component, the test passes.
Any idea how I should test that?
Rx.Observable.of seems to be synchronous (GitHub issue). That's why fakeAsync wrapper doesn't work with it.
Instead you can use e.g. Observable.fromPromise(Promise.resolve(...)).
In your case:
spy = spyOn(employeeService, 'getEmployees').and.returnValue(
Observable.fromPromise(Promise.resolve(testEmployees)));
Alternatively you can use async scheduler:
import { Scheduler } from 'rxjs/Rx';
...
spy = spyOn(employeeService, 'getEmployees').and.returnValue(
Observable.of(testEmployees, Scheduler.async));
I have prepared a working test sample on Plunkr