Merge pull request #223 from oke-py/refactor/testing-framework-vitest

refactor(testing): migrate from Jest to Vitest for testing framework
This commit is contained in:
Naoki Oketani
2025-05-02 23:25:18 +09:00
committed by GitHub
11 changed files with 3791 additions and 514 deletions

View File

@@ -30,7 +30,7 @@ This document outlines the development guidelines and best practices for our Typ
- **Testing**
- Write unit tests for all business logic
- Aim for high test coverage (at least 80%)
- Use Jest or Mocha for testing frameworks
- Use Vitest as the testing framework
- Implement integration tests for critical paths
- **Build Process**

View File

@@ -17,15 +17,14 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- name: Install dependencies and run all scripts
- name: Install dependencies and run tests
run: |
npm ci
npm run all
npm run test -- --run --coverage
- uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
# Adding a comment to clarify the purpose of the Windows build job
build-on-windows:
strategy:
matrix:
@@ -36,11 +35,10 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
# Node.js 20 already includes a recent npm version, so npm upgrade is not needed
- name: Install dependencies and run all scripts
- name: Install dependencies and run tests
run: |
npm ci
npm run all
npm run test -- --run
test: # make sure the action works on a clean machine without building
strategy:

View File

@@ -14,3 +14,25 @@
### GitHub REST API v3
- https://developer.github.com/v3/
## Development Instructions
### Running Tests
This project uses [Vitest](https://vitest.dev/) for testing. To run the tests, use the following command:
```bash
npm run test
```
Vitest will execute all test files and provide a detailed report of the results.
### Generating Coverage Reports
To generate a test coverage report, use the following command:
```bash
npm run test:coverage
```
The coverage report will be available in the `coverage` directory.

View File

@@ -73,6 +73,28 @@ jobs:
dedupe_issues: true
```
## Development
### Running Tests
This project uses [Vitest](https://vitest.dev/) for testing. To run the tests, use the following command:
```bash
npm run test
```
Vitest will execute all test files and provide a detailed report of the results. For coverage reports, you can use:
```bash
npm run test:coverage
```
Ensure all dependencies are installed before running the tests:
```bash
npm ci
```
- - -
This action is inspired by [homoluctus/gitrivy](https://github.com/homoluctus/gitrivy).

View File

@@ -3,17 +3,17 @@ import * as fs from 'fs'
import * as path from 'path'
import {Audit} from '../src/audit'
jest.mock('child_process')
vi.mock('child_process')
const audit = new Audit()
describe('run', () => {
beforeEach(() => {
jest.mocked(child_process).spawnSync.mockClear()
vi.mocked(child_process).spawnSync.mockClear()
})
test('finds vulnerabilities with default values', () => {
jest.mocked(child_process).spawnSync.mockImplementation((): any => {
vi.mocked(child_process).spawnSync.mockImplementation((): any => {
const stdout = fs.readFileSync(
path.join(__dirname, 'testdata/audit/error.txt')
)
@@ -34,7 +34,7 @@ describe('run', () => {
})
test('finds vulnerabilities with production flag enabled', () => {
jest.mocked(child_process).spawnSync.mockImplementation((): any => {
vi.mocked(child_process).spawnSync.mockImplementation((): any => {
const stdout = fs.readFileSync(
path.join(__dirname, 'testdata/audit/error.txt')
)
@@ -55,7 +55,7 @@ describe('run', () => {
})
test('finds vulnerabilities with json flag enabled', () => {
jest.mocked(child_process).spawnSync.mockImplementation((): any => {
vi.mocked(child_process).spawnSync.mockImplementation((): any => {
const stdout = fs.readFileSync(
path.join(__dirname, 'testdata/audit/error.json')
)
@@ -76,7 +76,7 @@ describe('run', () => {
})
test('does not find vulnerabilities', () => {
jest.mocked(child_process).spawnSync.mockImplementation((): any => {
vi.mocked(child_process).spawnSync.mockImplementation((): any => {
const stdout = fs.readFileSync(
path.join(__dirname, 'testdata/audit/success.txt')
)
@@ -97,7 +97,7 @@ describe('run', () => {
})
test('throws an error if error is not null', () => {
jest.mocked(child_process).spawnSync.mockImplementation((): any => {
vi.mocked(child_process).spawnSync.mockImplementation((): any => {
return {
pid: 100,
output: '',
@@ -115,7 +115,7 @@ describe('run', () => {
})
test('throws an error if status is null', () => {
jest.mocked(child_process).spawnSync.mockImplementation((): any => {
vi.mocked(child_process).spawnSync.mockImplementation((): any => {
return {
pid: 100,
output: '',
@@ -133,7 +133,7 @@ describe('run', () => {
})
test('throws an error if stderr is null', () => {
jest.mocked(child_process).spawnSync.mockImplementation((): any => {
vi.mocked(child_process).spawnSync.mockImplementation((): any => {
return {
pid: 100,
output: '',

View File

@@ -68,7 +68,7 @@ describe('getExistingIssueNumber', () => {
})
test('gets existing open issue', async () => {
const getIssues = jest.fn()
const getIssues = vi.fn()
getIssues.mockResolvedValue({
data: [
{
@@ -89,7 +89,7 @@ describe('getExistingIssueNumber', () => {
})
test('returns null when there is no open issue', async () => {
const getIssues = jest.fn()
const getIssues = vi.fn()
getIssues.mockResolvedValue({data: []})
const result = await issue.getExistingIssueNumber(getIssues, {repo, owner})
@@ -104,7 +104,7 @@ describe('getExistingIssueNumber', () => {
})
test('returns null when no issues match the issue title', async () => {
const getIssues = jest.fn()
const getIssues = vi.fn()
getIssues.mockResolvedValue({
data: [
{

View File

@@ -5,17 +5,17 @@ import {run} from '../src/main'
import * as issue from '../src/issue'
import * as pr from '../src/pr'
jest.mock('../src/audit')
jest.mock('../src/issue')
jest.mock('../src/pr')
jest.mock('@octokit/rest', () => {
vi.mock('../src/audit')
vi.mock('../src/issue')
vi.mock('../src/pr')
vi.mock('@octokit/rest', () => {
return {
Octokit: jest.fn().mockImplementation(() => {
Octokit: vi.fn().mockImplementation(() => {
return {
issues: {
listForRepo: jest.fn(),
createComment: jest.fn(),
create: jest.fn()
listForRepo: vi.fn(),
createComment: vi.fn(),
create: vi.fn()
}
}
})
@@ -25,8 +25,8 @@ jest.mock('@octokit/rest', () => {
describe('run: pr', () => {
beforeEach(() => {
// initialize mock
jest.mocked(Audit).mockClear()
jest.mocked(pr).createComment.mockClear()
vi.mocked(Audit).mockClear()
vi.mocked(pr).createComment.mockClear()
process.env.INPUT_AUDIT_LEVEL = 'low'
process.env.INPUT_PRODUCTION_FLAG = 'false'
@@ -39,7 +39,7 @@ describe('run: pr', () => {
})
test('does not call pr.createComment if vulnerabilities are not found', () => {
jest.mocked(Audit).mockImplementation((): any => {
vi.mocked(Audit).mockImplementation((): any => {
return {
stdout: fs.readFileSync(
path.join(__dirname, 'testdata/audit/success.txt')
@@ -57,14 +57,14 @@ describe('run: pr', () => {
}
})
jest.mocked(pr).createComment.mockResolvedValue()
vi.mocked(pr).createComment.mockResolvedValue()
expect(run).not.toThrowError()
expect(pr.createComment).not.toHaveBeenCalled()
})
test('calls pr.createComment if vulnerabilities are found in PR', () => {
jest.mocked(Audit).mockImplementation((): any => {
vi.mocked(Audit).mockImplementation((): any => {
return {
stdout: fs.readFileSync(
path.join(__dirname, 'testdata/audit/error.txt')
@@ -82,7 +82,7 @@ describe('run: pr', () => {
}
})
jest.mocked(pr).createComment.mockResolvedValue()
vi.mocked(pr).createComment.mockResolvedValue()
expect(run).not.toThrowError()
expect(pr.createComment).toHaveBeenCalled()
@@ -91,7 +91,7 @@ describe('run: pr', () => {
test('does not call pr.createComment if create_pr_comments is set to false', () => {
process.env.INPUT_CREATE_PR_COMMENTS = 'false'
jest.mocked(Audit).mockImplementation((): any => {
vi.mocked(Audit).mockImplementation((): any => {
return {
stdout: fs.readFileSync(
path.join(__dirname, 'testdata/audit/error.txt')
@@ -117,8 +117,8 @@ describe('run: pr', () => {
describe('run: issue', () => {
beforeEach(() => {
// initialize mock
jest.mocked(Audit).mockClear()
jest.mocked(issue).getExistingIssueNumber.mockClear()
vi.mocked(Audit).mockClear()
vi.mocked(issue).getExistingIssueNumber.mockClear()
process.env.INPUT_AUDIT_LEVEL = 'low'
process.env.INPUT_PRODUCTION_FLAG = 'false'
@@ -133,7 +133,7 @@ describe('run: issue', () => {
test('does not call octokit.rest.issues.create if create_issues is set to false', () => {
process.env.INPUT_CREATE_ISSUES = 'false'
jest.mocked(Audit).mockImplementation((): any => {
vi.mocked(Audit).mockImplementation((): any => {
return {
stdout: fs.readFileSync(
path.join(__dirname, 'testdata/audit/error.txt')
@@ -151,7 +151,7 @@ describe('run: issue', () => {
}
})
jest.mocked(issue).getExistingIssueNumber.mockResolvedValue(null)
vi.mocked(issue).getExistingIssueNumber.mockResolvedValue(null)
expect(run).not.toThrowError()
expect(issue.getExistingIssueNumber).not.toHaveBeenCalled()

View File

@@ -1,11 +0,0 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
}

4150
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,8 +13,9 @@
"format-check": "prettier --check **/*.ts",
"lint": "eslint src/**/*.ts",
"pack": "ncc build",
"test": "jest",
"all": "npm run build && npm run format && npm run lint && npm run pack && npm test -- --coverage"
"test": "vitest",
"test:coverage": "vitest --coverage",
"all": "npm run build && npm run format && npm run lint && npm run pack && npm run test:coverage"
},
"repository": {
"type": "git",
@@ -35,18 +36,17 @@
"strip-ansi": "^6.0.1"
},
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/node": "^20.0.0",
"@typescript-eslint/parser": "^6.7.4",
"@vercel/ncc": "^0.38.3",
"@vitest/coverage-v8": "^3.1.2",
"eslint": "^8.51.0",
"eslint-plugin-github": "^4.10.1",
"eslint-plugin-jest": "^27.4.2",
"jest": "^29.7.0",
"jest-circus": "^29.7.0",
"js-yaml": "^4.0.0",
"prettier": "^3.5.3",
"ts-jest": "^29.3.2",
"typescript": "^5.8.3"
"typescript": "^5.8.3",
"vite": "^6.3.4",
"vitest": "^3.1.2"
}
}

12
vitest.config.ts Normal file
View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['__tests__/**/*.test.ts'],
coverage: {
reporter: ['text', 'json', 'html', 'lcov'],
},
},
});