I have an issue with my unit test in my NestJs program. I am using Jest for the unit test, I have created a mocking method that is called, but Jest thinks otherwise.
I couldn't find the solution to this problemn using NestJs.
The test file themes.service.spec.ts:
...
/* Mock of Repository */
const mockThemesRepository = {
findOne: jest.fn().mockImplementation(id => {
return {id: id}
}),
remove: jest.fn().mockImplementation(dto => {
console.log("MESSAGE 2") // printed
console.log(dto) // printed
return {
dto
}
})
}
/* Inject the mock object */
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ThemesService,
{
provide: getRepositoryToken(Theme),
useValue: mockThemesRepository,
},
],
}).compile();
service = module.get<ThemesService>(ThemesService);
});
/* Test Remove method */
it('should delete a theme', async () => {
service.remove(1)
expect(mockThemesRepository.findOne).toHaveBeenCalledWith(1);
expect(mockThemesRepository.remove).toHaveBeenCalled()
})
...
The service file: themes.service.ts:
...
export class ThemesService {
constructor(
#InjectRepository(Theme)
private readonly themeRepository: Repository<Theme>
) {}
...
async remove(id: number): Promise<Theme> {
const theme = await this.themeRepository.findOne(id);
console.log("MESSAGE 1"); // printed
return this.themeRepository.remove(theme);
}
}
The error message:
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
103 | expect(mockThemesRepository.findOne).toHaveBeenCalledWith(1);
> 104 | expect(mockThemesRepository.remove).toHaveBeenCalled()
The test is called but Jest think it wasn't.
EDIT:
With the solution proposed by Jay McDoniel, the test works:
expect(mockThemesRepository.findOne).toHaveBeenCalledWith(1);
expect(await mockThemesRepository.remove).toHaveBeenCalled()
Related
I am having vue3 app with vite and vitest and trying to mock the Quasar useQuasar composable which I am using in my custom Composable like:
// useLoginRequestBuilder.ts
import { makeUserAuthentication } from "#/main/factories"
import { useQuasar } from "quasar"
export function useLoginRequestBuilder() {
const $q = useQuasar()
async function login() {
try {
$q.loading.show()
const auth = makeUserAuthentication()
return await auth.signinRedirect()
} catch (e) {
console.log(e)
$q.loading.hide()
$q.notify({
color: "red-4",
textColor: "white",
icon: "o_warning",
message: "Login Failed!",
})
}
}
return {
login,
}
}
and I am trying to mock quasar in tests like:
// useLoginRequestBuilder.spec.ts
import { useLoginRequestBuilder } from "#/main/builders"
vi.mock("quasar", () => ({ // <--- this is not really mocking quasar
useQuasar: () => ({
loading: {
show: () => true,
hide: () => true,
},
}),
}))
const spyAuth = vi.fn(() => Promise.resolve(true))
vi.mock("#/main/factories", () => ({
makeUserAuthentication: () => ({
signinRedirect: () => spyAuth(),
}),
}))
describe("test useLoginRequestBuilder", () => {
test("should call signinRedirect", async () => {
const { login } = useLoginRequestBuilder()
const sut = await login()
expect(sut).toBe(true)
})
})
vi.mock("quasar"... is failing to mock quasar and I am getting below error. That means, it failed to mock and failed to get the $q.loading.... object.
TypeError: Cannot read properties of undefined (reading 'loading')
I understand that there is a separate testing lib for quasar, here but I think this is not really the case here.
Bordering on a necro-post, but I had a similar issue that the mocking factory wasn't creating the plugins being used in non-Vue components, and had to mock each call individually in the end.
Though I'd add it here for anyone else
vitest.mock("quasar", () => vi.fn()); // this doesn't mock out calls
// use individual mocks as below
import { Loading } from "quasar";
vi.spyOn(Loading, "show").mockImplementation(() => vi.fn());
vi.spyOn(Loading, "hide").mockImplementation(() => vi.fn());
I want to write a unit test for my payment service but I'm receiving this error:
source.subscribe is not a function
at ./node_modules/rxjs/src/internal/lastValueFrom.ts:60:12
This is my service
import { HttpService } from '#nestjs/axios';
import { Injectable } from '#nestjs/common';
import { lastValueFrom } from 'rxjs';
import { PaymentInfo } from 'src/utils/types/paymentInfo';
#Injectable()
export class PaymentsService {
constructor(private readonly httpService: HttpService) {}
private createHeaderWithAuth(auth, contentType = 'application/json') {
return {
headers: {
authorization: auth.replace('Bearer', '').trim(),
'Content-Type': contentType,
},
};
}
async makePayment(auth: string, paymentInfo: PaymentInfo) {
const configs = this.createHeaderWithAuth(auth);
const response = await lastValueFrom(
await this.httpService.post(
`${process.env.PAYMENT_URL}/transaction/pay`,
paymentInfo,
configs
)
).catch((error) => {
console.log(error);
throw new Error(error.response.data.message);
});
return response.data;
}
}
So with a bit of searching and tinkering found out that this is caused by my import of a rxjs function to resolve the observable setted by axios.
I've searched ways to mock this function so I can properly test my service. But none of them gave me a solution, the questions i found only revolved around functions with modules, but these have none since is imported from a third party lib.
This is my test suite:
describe('Payments Service', () => {
let service: PaymentsService;
let mockedHttpService = {
post: jest
.fn()
.mockImplementation(
async (
url: string,
paymentInfo: PaymentInfo,
header = mockedHeader
) => {
return { mockedSuccessfulResponse };
}
),
get: jest
.fn()
.mockImplementation(async (url: string, header = mockedHeader) => {
return { ...mockedSuccessfulResponse, data: mockedUserCards };
}),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PaymentsService,
{
provide: HttpService,
useValue: mockedHttpService,
},
],
}).compile();
service = module.get<PaymentsService>(PaymentsService);
});
describe('Initialize', () => {
it('should define service', () => {
expect(service).toBeDefined();
});
describe('makePayment', () => {
it('should make a payment', async () => {
const payment = await service.makePayment(mockedAuth, mockedPaymentInfo);
expect(mockedHttpService.post).toHaveBeenCalledWith(
`${process.env.PAYMENT_URL}/transaction/pay`,
mockedPaymentInfo,
mockedHeader
);
expect(payment).toBe(mockedSuccessfulResponse);
});
});
});
Ps.: I removed the mocked objects to reduce the amount of code to read
you should use the of operator from rxjs, and drop the async keyword. Like:
.mockImplementation(
(
url: string,
paymentInfo: PaymentInfo,
header = mockedHeader
) => {
return of({ mockedSuccessfulResponse });
}
otherwise lastValueFrom won't receive an observable object.
My component calls
this.axios.get()
when being mounted and passes a vuex-store variable to the api. The api returns an array as the response and the component displays some of the returned data after exchanging a loading-element with the real content.
In my unit test I want to simulate the result of the axios-request, wait for the transition between the loading- and the content-element and then finally check the validity of the content. However, the test fails and outputs:
Cannot read property 'get' of undefined
and highlights the get on this.axios.
Here is what I'm expecting to work (based on this guide):
... some imports etc. ...
const mockAxios = { whatIExpectToGet };
jest.mock("axios", () => ({
get: jest.fn(() => mockAxios)
}));
it("description of the test", async () => {
const wrapper = mount(MyComponent);
... code continues ...
Of course I'm accesssing axios via this and not directly like they do in the guide. But, since I can't find any mention of anything related to that, I assume that's irrelevant?
I also tried to mock axios myself like so:
... imports etc. ...
const axios = {
get: Promise.resolve({ whatIExpectToGet })
};
it("description of the test", async () => {
const wrapper = mount(MyComponent, {
global: {
mocks: [ axios ]
}
});
... code continues ...
Apparently people with similar problems used localVue.use() to inject stuff, but that's no longer supported.
Could someone be so kind and smart as to point me into the right direction, please?
Thank you.
-------------------> SOLUTION <-------------------
Thanks to tony 19 this question is already solved.
I ended up using an async function to mock axios because Promise.resolve() wasn't working for me:
import { shallowMount, flushPromises } from "#vue/test-utils";
import MyComponent from "#/components/MyComponent.vue";
describe("MyComponent.vue", () => {
const axios = {
get: async () => ({
data: { expectedData }
})
};
it("test description", async () => {
const wrapper = shallowMount(MyComponent, {
global: {
mocks: {
axios: axios
}
}
} as any);
expect(wrapper.html()).toContain("some_string_i_display_while_loading");
await flushPromises();
expect(wrapper.html()).toContain("some_string_i_display_after_getting_the_response");
});
});
Using global.mocks to mock axios is the right approach, but your attempt incorrectly used an array when it should've been an object:
const wrapper = mount(MyComponent, {
global: {
// mocks: [ axios ] ❌
mocks: { axios } ✅
}
})
Note axios.get() resolves to an axios.Response object, which stores the response data in its data property, so your mock should do the same.
Here's a full example:
// MyComponent.vue
export default {
mounted() {
this.axios.get('foo').then(resp => this.foo = resp.data)
}
}
// MyComponent.spec.js
it('gets foo', () => {
const wrapper = mount(MyComponent, {
global: {
mocks: {
axios: {
get: Promise.resolve({ data: { foo: true }})
// OR use an async function, which internally returns a Promise
get: async () => ({ data: { foo: true }})
}
}
}
}
})
I'm using Jest to test a function from a service that uses axios to make some api calls. The problem is that Jest keeps calling the actual services function instead of the mocked service function. Here is all of the code:
The tests:
// __tests__/NotificationService.spec.js
const mockService = require('../NotificationService').default;
beforeEach(() => {
jest.mock('../NotificationService');
});
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
expect.assertions(1);
const data = await mockService.fetchNotifications();
console.log(data);
expect(data).toHaveProperty('data.bell');
});
});
The mock:
// __mocks__/NotificationService.js
const notifData = {
bell: false,
rollups: [
{
id: 'hidden',
modifiedAt: 123,
read: true,
type: 'PLAYLIST_SUBSCRIBED',
visited: false,
muted: false,
count: 3,
user: {
id: 'hidden',
name: 'hidden'
},
reference: {
id: 'hidden',
title: 'hidden',
url: ''
}
}
],
system: [],
total: 1
};
export default function fetchNotifications(isResolved) {
return new Promise((resolve, reject) => {
process.nextTick(() =>
isResolved ? resolve(notifData) : reject({ error: 'It threw an error' })
);
});
}
The service:
import axios from 'axios';
// hardcoded user guid
export const userId = 'hidden';
// axios instance with hardcoded url and auth header
export const instance = axios.create({
baseURL: 'hidden',
headers: {
Authorization:
'JWT ey'
}
});
/**
* Notification Service
* Call these methods from the Notification Vuex Module
*/
export default class NotificationService {
/**
* #GET Gets a list of Notifications for a User
* #returns {AxiosPromise<any>}
* #param query
*/
static async fetchNotifications(query) {
try {
const res = await instance.get(`/rollups/user/${userId}`, {
query: query
});
return res;
} catch (error) {
console.error(error);
}
}
}
I've tried a couple of variations of using require instead of importing the NotificationService, but it gave some other cryptic errors...
I feel like I'm missing something simple.
Help me please :)
The problem is that Jest keeps calling the actual services function instead of the mocked service function.
babel-jest hoists jest.mock calls so that they run before everything else (even import calls), but the hoisting is local to the code block as described in issue 2582.
I feel like I'm missing something simple.
Move your jest.mock call outside the beforeEach and it will be hoisted to the top of your entire test so your mock is returned by require:
const mockService = require('../NotificationService').default; // mockService is your mock...
jest.mock('../NotificationService'); // ...because this runs first
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
...
});
});
In my Angular 2 application, I'm trying to unit test the following component:
export class LoginComponent implements OnInit {
invalidCredentials = false;
unreachableBackend = false;
constructor(private authService: AuthService) {}
ngOnInit() {
this.invalidCredentials = false;
}
onSubmit(user: any) {
this.authService.authenticateUser(<User>user).subscribe((result) => {
if (!result) {
this.invalidCredentials = true;
}
}, (error) => {
if (error instanceof InvalidCredentialsError) {
this.invalidCredentials = true;
} else {
this.unreachableBackend = true;
}
});
}
}
I have already successfully tested the happy path. Now I would like to check that when authService.authenticateUser() throws an error, invalidCredentials and unreachableBackend are correctly set. Here is what I am trying:
describe('Authentication triggering an error', () => {
class FakeAuthService {
authenticateUser(user: User) {
throw new InvalidCredentialsError();
}
}
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
let authService: AuthService;
let spy: Spy;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [LoginComponent],
providers: [
{provide: AuthService, useClass: FakeAuthService},
{provide: TranslateService, useClass: FakeTranslateService}
],
imports: [ FormsModule, TranslateModule, AlertModule ]
});
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
authService = fixture.debugElement.injector.get(AuthService);
spy = spyOn(authService, 'authenticateUser').and.callThrough();
});
it('should not log in successfully if authentication fails', () => {
const user = {username: 'username', password: 'password'};
component.onSubmit(user);
expect(authService.authenticateUser).toHaveBeenCalledWith(user);
expect(spy.calls.count()).toEqual(1, 'authenticateUser should have been called once');
expect(component.invalidCredentials).toBe(true, 'credentials should be invalid because of the exception');
expect(component.unreachableBackend).toBe(false, 'backend should be reachable at first');
});
});
But when I run this test, I get the following failure:
PhantomJS 2.1.1 (Mac OS X 0.0.0) Component: Login Authentication triggering an error should not log in successfully if authentication fails FAILED
[object Object] thrown in src/test.ts (line 49782)
authenticateUser#webpack:///Users/sarbogast/dev/myproject/frontend/src/app/login/login.component.spec.ts:112:44 <- src/test.ts:49782:67
onSubmit#webpack:///Users/sarbogast/dev/myproject/frontend/src/app/login/login.component.ts:9:4896 <- src/test.ts:85408:5955
webpack:///Users/sarbogast/dev/myproject/frontend/src/app/login/login.component.spec.ts:139:25 <- src/test.ts:49806:31
invoke#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:203:0 <- src/test.ts:84251:33
onInvoke#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/proxy.js:72:0 <- src/test.ts:59204:45
invoke#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:202:0 <- src/test.ts:84250:42
run#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:96:0 <- src/test.ts:84144:49
webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/jasmine-patch.js:91:27 <- src/test.ts:58940:53
execute#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/jasmine-patch.js:119:0 <- src/test.ts:58968:46
execute#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/jasmine-patch.js:119:0 <- src/test.ts:58968:46
invokeTask#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:236:0 <- src/test.ts:84284:42
runTask#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:136:0 <- src/test.ts:84184:57
drainMicroTaskQueue#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:368:0 <- src/test.ts:84416:42
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 33 of 38 (1 FAILED) (skipped 5) (0.704 secs / 0.842 secs)
So obviously there is something I didn't get. I should mention the fact that I'm completely new to JS unit testing and somewhat new to reactive programming.
You can't do that as the error will just be thrown and bubble up to the test. What you need to do is allow the user the subscribe with callbacks, and then you can call the error callback. For example
class FakeAuthService {
authenticateUser(user: User) {
// return this service so user can call subscribe
return this;
}
subscribe(onNext, onError) {
if (onError) {
onError(new InvalidCredentialsError());
}
}
}
Another option is to use Observable.throw(new InvalidCredentialsError())
authenticateUser(user: User) {
return Observable.throw(new InvalidCredentialsError());
}
This will cause any subscriber's onError callback to be called. Personally I like using the mock returning itself. I can make the mock more configurable to make testing easier. See the link below for an example of what I mean.
See also:
Angular 2 - Testing error case with observables in services