美文网首页
间接输入

间接输入

作者: sweetBoy_9126 | 来源:发表于2024-09-21 16:54 被阅读0次

依赖函数调用

  • index.ts
import {userAge} from './user'

export function doubleUserAge(): number {
  return userAge() * 2
}
  • user.ts
export function userAge() {
  return 18
}

export function fetchUserAge() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      return resolve(18)
    }, 0)
  })
}
  • case.spec.ts
import { doubleUserAge } from ".";
import {it, expect, describe} from 'vitest'

describe('间接input', () => {
  it('first', () => {
    const r = doubleUserAge()
    expect(r).toBe(36)
  })
})

问题:我们 userAge 里的值可能变,如果一旦修改了,那么我们这个单元测试就不通过了
解决方式:我们是否可以控制间接 Input 的值(userAge)

使用 vitest vi

  • case.sepc.ts
import { doubleUserAge } from ".";
import {vi, it, expect, describe} from 'vitest'

vi.mock('./user.ts', () => {
  return {
    userAge: () => 2
  }
})
describe('间接input', () => {
  it('first', () => {
    const r = doubleUserAge()
    expect(r).toBe(4)
  })
})
  1. 只要在外层使用 vi 需修改了 user
    age 里的值,在这个测试 case 里全局都会改变
it('second', () => {
    // 2
    console.log(userAge())
  })
  1. 使用了 vi.mock 在编译的时候会提到最顶部
    console.log(userAge()) // 2
vi.mock('./user.ts', () => {
  return {
    userAge: () => 2
  }
})
  1. 不想让当前的测试用例文件共享一个值
  • 使用 mocked
vi.mock('./user.ts')
describe('间接input', () => {
  it('first', () => {
    vi.mocked(userAge).mockReturnValue(2)
    const r = doubleUserAge()
    expect(r).toBe(4)
  })
  it('second', () => {
    vi.mocked(userAge).mockReturnValue(4)
    console.log(userAge()) // 4
  })
})
  • 使用 doMock
describe('间接input', () => {
  it('first', async () => {
    vi.doMock('./user', () => {
      return {
        userAge: () => 2
      }
    })
    const {doubleUserAge} = await import('./index')
    const r = doubleUserAge()
    expect(r).toBe(4)
  })
  it('second', () => {
    console.log(userAge()) // oldValue
  })
})

优化

describe('间接input', () => {
  beforeEach(() => {

    vi.doMock('./user', () => {
      return {
        userAge: () => 2
      }
    })
  })
  it('first', async () => {
    const {doubleUserAge} = await import('./index')
    const r = doubleUserAge()
    expect(r).toBe(4)
  })
  it('second', () => {
    console.log(userAge()) // oldValue
  })
})
  1. 处理异步
  • index.ts
import {fetchUserAge, userAge} from './user'

export function doubleUserAge(): number {
  return userAge() * 2
}
export async function fetchDoubleUserAge() :Promise<number> {
  const userAge = await fetchUserAge()
  return userAge * 2
}
  • user.ts
export function userAge() {
  return 18
}

export function fetchUserAge(): Promise<number> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      return resolve(18)
    }, 0)
  })
}
  • case.spec.ts
import { doubleUserAge, fetchDoubleUserAge } from ".";
import {vi, it, expect, describe, beforeEach} from 'vitest'
import { userAge } from "./user";
vi.mock('./user', () => {
  return {
    fetchUserAge: () => Promise.resolve(2)
  }
})
describe('间接input', () => {
  beforeEach(() => {

    vi.doMock('./user', () => {
      return {
        userAge: () => 2
      }
    })
  })
  it('first', async () => {
    const r = await fetchDoubleUserAge()
    expect(r).toBe(4)
  })
  it('second', () => {
    // console.log(userAge()) // oldValue
  })
})

第三方库

  • third-party-modules.ts
import axios from 'axios'
interface User {
  name: string;
  age: number
}
export async function doubleUserAge() {
  const user: User = await axios('/user/1')
  return user.age * 2
}
  • spec.ts
import {test, vi, expect} from 'vitest'
import { doubleUserAge } from './third-party-modules'
import axios from 'axios'
vi.mock('axios')

test('第三模块的处理 axios', async () => {
  vi.mocked(axios).mockResolvedValue({name: 'lifa', age: 18})
  const r = await doubleUserAge()
  expect(r).toBe(36)
})
  • get 或 post
export async function doubleUserAge() {
  const user: User = await axios.get('/user/1')
  return user.age * 2
}

test('第三模块的处理 axios', async () => {
  vi.mocked(axios.get).mockResolvedValue({name: 'lifa', age: 18})
  const r = await doubleUserAge()
  expect(r).toBe(36)
})

对象

对象属性

  • use-class.ts
import {User} from './UserClass'
export function doubleUserAge() :number {
  const user = new User()
  console.log(user)
  return user.age * 2
}
  • useClass.ts
export class User {
  age: number = 18;
  name: string = 'lifa'
  getAge() {
    return this.age
  }
}
  • spec.ts
import {it, expect, describe, vi} from 'vitest'
import { doubleUserAge } from './use-class'
vi.mock('./UserClass', () => {
  return {
    User: class User {
      age: number = 2
    }
  }
})
describe('使用 class 形式', () => {
  it('属性', () => {
    const r = doubleUserAge()
    expect(r).toBe(4)
  })
})

方法

describe('使用 class 形式', () => {
 // it('属性', () => {
  //   const r = doubleUserAge()
  //   expect(r).toBe(4)
  // })
  it('方法', () => {
    User.prototype.getAge = () => {
      return 2
    }

    const r = doubleUserAge()
    expect(r).toBe(4)
  })
})

变量

// user-variable.ts
import {name, gold} from './config'
export function tellName() {
  console.log(gold)
  return name + '-heiheihei'
}

// user-variable.spec.ts
import {it, expect, describe, vi} from 'vitest'
import { tellName } from './user-variable'
vi.mock('./config', () => {
  return {
    name: 'c'
  }
})
describe('使用变量的形式', () => {
  it('tell me', () => {
    const r = tellName()
    expect(r).toBe('c-heiheihei')
  })
})

问题这样虽然我们的测试能通过,但是因为我们 mock 改写了返回值,我们 mock 里只返回了 name,所以我们在 tellName 里打印 gold 就会报错,因为mock里没返回 gold,它把我们之前的值覆盖了

如果我们只想改变其中某个值,还想保留之前的其他的属性和方法,那么直接通过 mock 的工厂函数里的参数获取就可以了

vi.mock('./config', async (importOriginal) => {
  const config = await importOriginal()
  return {
    ...config as any,
    name: 'c'
  }
})

// 也可以
vi.mock('./config', async (importOriginal) => {
  const config = await vi.importActual('./config')
  return {
    ...config as any,
    name: 'c'
  }
})

环境变量

  1. process
// env.ts
export function doubleUserAge() {
  return Number(process.env.USER_AGE) * 2
}

// env.spec.ts
import {it, expect, vi, afterEach} from 'vitest'
import { doubleUserAge } from './env'
afterEach(() => {
  // 每个case结束后都重置环境变量
  vi.unstubAllEnvs()
})

it('process', () => {
  // process.env.USER_AGE = '2'
  vi.stubEnv('USER_AGE', '2')
  const r = doubleUserAge()
  expect(r).toBe(4)
})
  1. import.meta
    同样也是用 vi.stubEnv 和 unstubAllEnvs 这两个方法
it('import.meta', () => {
  vi.stubEnv('VITE_USER_AGE', '2')
  const r = doubleUserAge()
  expect(r).toBe(4)
})

全局 global

// global.ts
export function doubleUserAge() {
  return lifa.age * 2
}
export function doubleHeight() {
  return innerHeight * 2
}

// spec.ts
import {vi, it, expect, describe} from 'vitest'
import { doubleHeight, doubleUserAge } from './global'

describe('global', () => {
  it('double user age', () => {
    vi.stubGlobal('lifa', {age: 18})

    const r = doubleUserAge()

    expect(r).toBe(36)
  })
  it('double inner height', () => {
    vi.stubGlobal('innerHeight', 100)
    const r = doubleHeight()
    expect(r).toBe(200)
  })
})

间接层的处理技巧

// window.ts
export function innerHeightFn() {
  return innerHeight
}

// global.ts
import { innerHeightFn } from "./window"
export function doubleHeight() {
  return innerHeightFn() * 2
}

// spec.ts
vi.mock('./window.ts', () => {
  return {
    innerHeightFn: () => 200,
  }
})
it('function', () => {
    const r = doubleHeight()
    expect(r).toBe(400)
  })

依赖注入

将依赖的模块通过参数传入,替换掉直接的依赖

1. 函数实现

比如:

import {readFileSync} from 'fs'
export function readAndProcessFile(filePath: string): string {
  const content: string = readFileSync(filePath, {encoding: 'utf-8'})
  return content + ''-> test unit"
} 

使用依赖注入的方法将依赖的 readFileSync 通过参数传入

export function readAndProcessFile(filePath: string, fileReader): string {
  const content: string = fileReader.read(filePath)
  return content + ''-> test unit"
} 

调用

import {readAndProcessFile} from './readAndProcessFile'
import {readFileSync} from 'fs'
class FileReader {
  read(filePath: string) {
    readFileSync(filePath, {encoding: 'utf-8'})
  }
}
const result = readAndProcessFile('example.txt', new FileReader())

对应的测试用例

import {it, expect, describe} from 'vitest'
import {readAndProcessFile} from './readAndProcessFile'

describe('di function', () => {
  it('read and process file', () => {
    class StubFileReader {
      read(filePath: string) {
        return 'lifa'
      }
    }
    const result = readAndProcessFile('./test', new StubFileReader())
    expect(result).toBe('lifa-> test unit')
  })
})
  • 依赖倒置原则
    高层模块不应该依赖低层模块,两者都应该依赖其抽象
    抽象不应该依赖细节,细节应该依赖于抽象



    上面的a就是我们的 readAndProcessFile.ts 文件,fs 模块就是 b,a又叫做高层模块,b是低层模块,倒置后,a依赖于接口,b是实现接口

class 实现

改造前:

import { readFileSync } from 'fs'
export class ReadAndProcessFile {
  run(filePath: string) {
    const content = readFileSync(filePath, {encoding: 'utf-8'})
    return content + '->unit test'
  }
}

方式1:构造函数
把依赖通过构造器传过来

interface FileReader {
  read(filePath: string): string
}
export class ReadAndProcessFile {
  private _fileReader: FileReader
  constructor(fileReader: FileReader) {
    this._fileReader = fileReader
  }
  run(filePath: string) {
    const content = this._fileReader.read(filePath)
    return content + '->unit test'
  }
}

对应测试用例

import {it, expect, describe} from 'vitest'
import {ReadAndProcessFile, FileReader} from './ReadAndProcessFile'

describe('di class', () => {
  it('构造函数', () => {
    class StubFileReader implements FileReader {
      read(filePath: string): string {
        return 'lifa'
      }
    }
    const readAndProcessFile = new ReadAndProcessFile(new StubFileReader())
    expect(readAndProcessFile.run('./test')).toBe('lifa->unit test')
  })
})

方式2:属性

export class ReadAndProcessFile {
  private _fileReader: FileReader
  run(filePath: string) {
    const content = this._fileReader.read(filePath)
    return content + '->unit test'
  }
  set fileReader(fileReader: FileReader) {
    this._fileReader = fileReader
  }
}

对应用例:

  it('属性', () => {

    class StubFileReader implements FileReader {
      read(filePath: string): string {
        return 'lifa'
      }
    }
    const readAndProcessFile = new ReadAndProcessFile()
    readAndProcessFile.fileReader = new StubFileReader()
    expect(readAndProcessFile.run('./test')).toBe('lifa->unit test')
  })

方式3:方法

export class ReadAndProcessFile {
  private _fileReader: FileReader
  run(filePath: string) {
    const content = this._fileReader.read(filePath)
    return content + '->unit test'
  }
  setFileReader(fileReader: FileReader) {
    this._fileReader = fileReader
  }
}

测试用例

  it('方法', () => {

    class StubFileReader implements FileReader {
      read(filePath: string): string {
        return 'lifa'
      }
    }
    const readAndProcessFile = new ReadAndProcessFile()
    readAndProcessFile.setFileReader(new StubFileReader())
    expect(readAndProcessFile.run('./test')).toBe('lifa->unit test')
  })

相关文章

  • 可编辑区域文本插入

    键盘输入分类 直接输入 输入的键直接落入可输入DOM元素,为直接输入。 E.g.英文输入。 间接输入 输入的键值不...

  • 根据月份累加金额

    间接法1.根据输入日期连接为所需要格式

  • 间接的间接

    各种朋友圈和微信签名的,间接的,交流。 也蛮好。 安好

  • 间接

    突然想到... 一些间接的自以为有逻辑的“test”手段, 是不是刚好说明了用这种手段的人的判断能力的薄弱, 和“...

  • 基于JDBC的SQL注入问题

    sql注入问题:用户输入的信息,间接或者直接的参与了,sq语句的拼写,影响了语句的判断 问题: -- 早年登录逻辑...

  • 间接感受

    很多时候我总是觉得自己不知道做什么,就算别人给我指了一条路,我还是一直问自己我要做什么。 时间长了我发现了,...

  • 间接费用

    主要的间接费用有: 制造费用,管理费用,销售与配送费用。

  • 间接kiss

    赵天宇轻轻松松拧开一瓶水,顺手递给孟子坤“哇!打开了!”看着孟子坤开心的表情,赵天宇不禁勾了勾嘴角。孟子坤看着拧开...

  • 间接赞美

    刘琳坚持第1213天分享(2020/9/6) 间接赞美是运用当事人有关的其他人的观点来进行赞美,可以分成两...

  • 📝间接灸

    1.艾炷灸:瘢痕灸--哮喘、肺痨、瘰疬等慢性病。无瘢痕灸--一般寒性疾病均可使用。隔姜灸--寒性呕吐、腹痛、腹泻以...

网友评论

      本文标题:间接输入

      本文链接:https://www.haomeiwen.com/subject/kkozljtx.html