最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

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

programmeradmin0浏览0评论

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)
})
发布评论

评论列表(0)

  1. 暂无评论