javascript - Mocking an object method of an instance in JEST to use it as a dependency injection - Stack Overflow

admin2025-04-16  4

I have the http class:

class http {
constructor() {}

public async request(url: string, options: RequestInit): Promise<Response> {
    const response = await fetch(`${url}`, options)
    return response
}

public async get(url: string): Promise<Response> {
    return this.request(url, { method: 'GET' })
}

public async post(url: string, data?: any): Promise<Response> {
    return this.request(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
    })
}

public async put(url: string, data?: any): Promise<Response> {
    return this.request(url, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
    })
}

public async delete(url: string): Promise<Response> {
    return this.request(url, { method: 'DELETE' })
}

}

export default http

Then i use the http class inside Core as an injected dependency

export class Core {
public http: http

constructor(http: http) {
    this.http = http
}

public async getUserDomainNameEntry(
    username: string,
    domainUrl: string,
): Promise<IDomainNameEntry | undefined> {
    const response = await this.http.get(
        `${domainUrl}/api/v1/dns/search/username/${username}`,
    )

    if (response.status === 404 || !response.ok) {
        console.log(response)
        return undefined
    }

    const dnsEntry: IDomainNameEntry = await response.json()
    return dnsEntry
}
}

This is my jest test:

import { Core } from '.'
import http from '../http'

it('Domain Name Entry Test', async () => {
    http.prototype.get = jest.fn(async (_url: string) =>
        Promise.resolve({
            ok: true,
            status: 200,
            json: async () => ({ name: 'javierhersan.stw', urls: [], ips: [] }),
        } as Response),
    )

    const core = new Core(new http())
    const domainNameEntry = await core.getUserDomainNameEntry(
        'javierhersan.stw',
        'http://localhost:3000',
    )

    expect(domainNameEntry).toBeDefined()
    if (domainNameEntry) {
        expect(domainNameEntry).toHaveProperty('name')
        expect(domainNameEntry).toHaveProperty('urls')
        expect(domainNameEntry).toHaveProperty('ips')
    }
})

Error

TypeError: fetch failed

  3 |
  4 |       public async request(url: string, options: RequestInit): Promise<Response> {
> 5 |               const response = await fetch(`${url}`, options)
    |                                ^
  6 |               return response
  7 |       }
  8 |

  at http.request (src/http/index.ts:5:20)
  at Core.getUserDomainNameEntry (src/core/index.ts:172:20)
  at Object.<anonymous> (src/core/index.test.ts:116:27)

Why is my mock method not overriding my http get original method. What is the best way of mocking an object method and injecting it as a dependency with jest? I have tested several ways and is not working is always calling the original get method, why?

I have the http class:

class http {
constructor() {}

public async request(url: string, options: RequestInit): Promise<Response> {
    const response = await fetch(`${url}`, options)
    return response
}

public async get(url: string): Promise<Response> {
    return this.request(url, { method: 'GET' })
}

public async post(url: string, data?: any): Promise<Response> {
    return this.request(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
    })
}

public async put(url: string, data?: any): Promise<Response> {
    return this.request(url, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
    })
}

public async delete(url: string): Promise<Response> {
    return this.request(url, { method: 'DELETE' })
}

}

export default http

Then i use the http class inside Core as an injected dependency

export class Core {
public http: http

constructor(http: http) {
    this.http = http
}

public async getUserDomainNameEntry(
    username: string,
    domainUrl: string,
): Promise<IDomainNameEntry | undefined> {
    const response = await this.http.get(
        `${domainUrl}/api/v1/dns/search/username/${username}`,
    )

    if (response.status === 404 || !response.ok) {
        console.log(response)
        return undefined
    }

    const dnsEntry: IDomainNameEntry = await response.json()
    return dnsEntry
}
}

This is my jest test:

import { Core } from '.'
import http from '../http'

it('Domain Name Entry Test', async () => {
    http.prototype.get = jest.fn(async (_url: string) =>
        Promise.resolve({
            ok: true,
            status: 200,
            json: async () => ({ name: 'javierhersan.stw', urls: [], ips: [] }),
        } as Response),
    )

    const core = new Core(new http())
    const domainNameEntry = await core.getUserDomainNameEntry(
        'javierhersan.stw',
        'http://localhost:3000',
    )

    expect(domainNameEntry).toBeDefined()
    if (domainNameEntry) {
        expect(domainNameEntry).toHaveProperty('name')
        expect(domainNameEntry).toHaveProperty('urls')
        expect(domainNameEntry).toHaveProperty('ips')
    }
})

Error

TypeError: fetch failed

  3 |
  4 |       public async request(url: string, options: RequestInit): Promise<Response> {
> 5 |               const response = await fetch(`${url}`, options)
    |                                ^
  6 |               return response
  7 |       }
  8 |

  at http.request (src/http/index.ts:5:20)
  at Core.getUserDomainNameEntry (src/core/index.ts:172:20)
  at Object.<anonymous> (src/core/index.test.ts:116:27)

Why is my mock method not overriding my http get original method. What is the best way of mocking an object method and injecting it as a dependency with jest? I have tested several ways and is not working is always calling the original get method, why?

Share Improve this question asked Feb 2 at 16:39 javierhersanjavierhersan 234 bronze badges 1
  • Given the error stack trace, it appears that getUserDomainNameEntry directly calls http.request, not http.get as it does in the code you've shown us – Bergi Commented Feb 2 at 19:25
Add a comment  | 

1 Answer 1

Reset to default 1

You should use jest mocks to mock your http class instead of modifying the prototype:

https://jestjs.io/docs/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock

import { Core } from '.'
import http from '../http'
jest.mock('../http', () => {
  return jest.fn().mockImplementation(() => ({
    async get(url) { 
      return { ok: true } 
    }
  }))
})

But I don't think this is a good test to do.

In a unit test you want to test your components in isolation. If you're testing Core, you want to test just Core and mock any dependencies it has. In this case Http. A separate unit test will take care of that.

Another way to say this: In this test we don't care what Http does internally. We only care about what Core does internally and how it interacts with Http.

We do this my mocking your Http instance (not class) and checking that Core calls Http correctly.

import { Core } from '.'

it('Domain Name Entry Test', async () => {
    const mockResponse = { name: 'javierhersan.stw', urls: [], ips: [] }
    const mockGet = jest.fn().mockImplementation(async url => ({
      ok: true,
      status: 200,
      json: async () => mockResponse,
    }))
    const mockHttp = {
      get: mockGet
    }

    const core = new Core(mockHttp)
    const domainNameEntry = await core.getUserDomainNameEntry(
      'javierhersan.stw',
      'http://localhost:3000',
    )

    expect(mockGet).toBeCalledWith('http://localhost:3000/api/v1/dns/search/username/javierhersan.stw')
    expect(domainNameEntry).toEqual(mockResponse)
})
转载请注明原文地址:http://anycun.com/QandA/1744794059a87713.html