Skip to content

Commit

Permalink
Fix test layout
Browse files Browse the repository at this point in the history
  • Loading branch information
johlju committed Sep 10, 2024
1 parent ae2d92e commit 5fb7b3c
Showing 1 changed file with 169 additions and 150 deletions.
319 changes: 169 additions & 150 deletions __tests__/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,178 +1,197 @@
import { describe, it, expect, jest, beforeEach } from '@jest/globals';

const context = describe;

import * as fs from 'fs';
import * as path from 'path';
import { loadConfig } from '../src/config';
import { RelabelerConfig } from '../src/types/relabeler-config';
import * as yaml from 'js-yaml';

// Mock the fs, path, and js-yaml modules
// This is hoisted to the top of the file automatically. It runs before any of the actual code in the test file. Affects all tests in the file.
// Because it's hoisted, it's executed before the module system is set up. This means it can affect imports throughout your entire test file.
jest.mock('fs');
jest.mock('path');
jest.mock('js-yaml');

// Create a mock instance of Ajv with compile and errorsText methods
const mockAjvInstance = {
compile: jest.fn(),
errorsText: jest.fn(),
};

// Mock the Ajv module
jest.doMock('ajv', () => ({
__esModule: true,
// Create a mock Ajv class
default: class MockAjv {
compile() {
// Return a function that logs the data and always returns true
return (data: any) => {
console.log('Validating data:', data);
return true; // Always return true for now
};
}
errorsText() {
// Always return 'Mock error' when errorsText is called
return 'Mock error';
}
}
}));

describe('loadConfig', () => {
// Define a mock configuration object
const mockConfig: RelabelerConfig = {
pulls: {
labels: [
{
label: {
name: 'test-label',
add: [{ when: { canBeMerged: true } }],
remove: [{ when: { canBeMerged: false } }],
// Define a mock configuration object. Defined at the test suite level, outside of any specific test.
let mockConfig: RelabelerConfig;

beforeAll(() => {
// Set mockConfig before all tests
mockConfig = {
pulls: {
labels: [
{
label: {
name: 'test-label',
add: [{ when: { canBeMerged: true } }],
remove: [{ when: { canBeMerged: false } }],
},
},
},
],
},
};

// Reset all mocks before each test
beforeEach(() => {
jest.resetAllMocks();
],
},
};
});

it('should load config from .github folder', () => {
// Mock path.join to simply join arguments with '/'
(path.join as jest.Mock).mockImplementation((...args) => args.join('/'));
// Mock fs.existsSync to return true only for paths containing '.github'
(fs.existsSync as jest.Mock).mockImplementation((path: unknown) => typeof path === 'string' && path.includes('.github'));
// Mock fs.readFileSync to return a stringified version of mockConfig
(fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockConfig));
// Mock yaml.load to return mockConfig
(yaml.load as jest.Mock).mockReturnValue(mockConfig);

// Call the function we're testing
const result = loadConfig('/repo/path');

// Assert that the result matches our mockConfig
expect(result).toEqual(mockConfig);
// Check that fs.existsSync was called with the correct path
expect(fs.existsSync).toHaveBeenCalledWith('/repo/path/.github/relabeler.config.yml');
// Check that fs.readFileSync was called with the correct path and encoding
expect(fs.readFileSync).toHaveBeenCalledWith('/repo/path/.github/relabeler.config.yml', 'utf8');
context('When loading config', () => {
beforeEach(() => {
// Reset all mocks before each test
jest.resetAllMocks();
});

it('should load config from .github folder', () => {
// Mock path.join to simply join arguments with '/'
(path.join as jest.Mock).mockImplementation((...args) => args.join('/'));
// Mock fs.existsSync to return true only for paths containing '.github'
(fs.existsSync as jest.Mock).mockImplementation((path: unknown) => typeof path === 'string' && path.includes('.github'));
// Mock fs.readFileSync to return a stringified version of mockConfig
(fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockConfig));
// Mock yaml.load to return mockConfig
(yaml.load as jest.Mock).mockReturnValue(mockConfig);

// Call the function we're testing
const result = loadConfig('/repo/path');

// Assert that the result matches our mockConfig
expect(result).toEqual(mockConfig);
// Check that fs.existsSync was called with the correct path
expect(fs.existsSync).toHaveBeenCalledWith('/repo/path/.github/relabeler.config.yml');
// Check that fs.readFileSync was called with the correct path and encoding
expect(fs.readFileSync).toHaveBeenCalledWith('/repo/path/.github/relabeler.config.yml', 'utf8');
});

it('should load config from root folder if not found in .github', () => {
// Mock path.join to simply join arguments with '/'
(path.join as jest.Mock).mockImplementation((...args) => args.join('/'));
// Mock fs.existsSync to return true only for paths not containing '.github'
(fs.existsSync as jest.Mock).mockImplementation((path: unknown) => typeof path === 'string' && !path.includes('.github'));
// Mock fs.readFileSync to return a stringified version of mockConfig
(fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockConfig));
// Mock yaml.load to return mockConfig
(yaml.load as jest.Mock).mockReturnValue(mockConfig);

// Call the function we're testing
const result = loadConfig('/repo/path');

// Assert that the result matches our mockConfig
expect(result).toEqual(mockConfig);
// Check that fs.existsSync was called for both .github and root paths
expect(fs.existsSync).toHaveBeenCalledWith('/repo/path/.github/relabeler.config.yml');
expect(fs.existsSync).toHaveBeenCalledWith('/repo/path/relabeler.config.yml');
// Check that fs.readFileSync was called with the correct path and encoding
expect(fs.readFileSync).toHaveBeenCalledWith('/repo/path/relabeler.config.yml', 'utf8');
});
});

it('should load config from root folder if not found in .github', () => {
// Mock path.join to simply join arguments with '/'
(path.join as jest.Mock).mockImplementation((...args) => args.join('/'));
// Mock fs.existsSync to return true only for paths not containing '.github'
(fs.existsSync as jest.Mock).mockImplementation((path: unknown) => typeof path === 'string' && !path.includes('.github'));
// Mock fs.readFileSync to return a stringified version of mockConfig
(fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockConfig));
// Mock yaml.load to return mockConfig
(yaml.load as jest.Mock).mockReturnValue(mockConfig);

// Call the function we're testing
const result = loadConfig('/repo/path');

// Assert that the result matches our mockConfig
expect(result).toEqual(mockConfig);
// Check that fs.existsSync was called for both .github and root paths
expect(fs.existsSync).toHaveBeenCalledWith('/repo/path/.github/relabeler.config.yml');
expect(fs.existsSync).toHaveBeenCalledWith('/repo/path/relabeler.config.yml');
// Check that fs.readFileSync was called with the correct path and encoding
expect(fs.readFileSync).toHaveBeenCalledWith('/repo/path/relabeler.config.yml', 'utf8');
});

it('should throw an error if config file is not found', () => {
// Mock fs.existsSync to always return false
(fs.existsSync as jest.Mock).mockReturnValue(false);
context('When config file is not found', () => {
it('should throw an error if config file is not found', () => {
// Mock fs.existsSync to always return false
(fs.existsSync as jest.Mock).mockReturnValue(false);

// Assert that calling loadConfig throws an error with the correct message
expect(() => loadConfig('/repo/path')).toThrow('Config file not found');
// Assert that calling loadConfig throws an error with the correct message
expect(() => loadConfig('/repo/path')).toThrow('Config file not found');
});
});

it('should parse YAML config file', () => {
// Mock fs.existsSync to return true
(fs.existsSync as jest.Mock).mockReturnValue(true);
// Mock fs.readFileSync to return 'yaml content'
(fs.readFileSync as jest.Mock).mockReturnValue('yaml content');
// Mock yaml.load to return mockConfig
(yaml.load as jest.Mock).mockReturnValue(mockConfig);

// Call the function we're testing
const result = loadConfig('/repo/path');

// Assert that the result matches our mockConfig
expect(result).toEqual(mockConfig);
// Check that yaml.load was called with 'yaml content'
expect(yaml.load).toHaveBeenCalledWith('yaml content');
});

it('should validate config against schema', () => {
// Mock fs.existsSync to return true
(fs.existsSync as jest.Mock).mockReturnValue(true);
// Mock fs.readFileSync to return 'yaml content'
(fs.readFileSync as jest.Mock).mockReturnValue('yaml content');
// Mock yaml.load to return mockConfig
(yaml.load as jest.Mock).mockReturnValue(mockConfig);

// Call the function we're testing
loadConfig('/repo/path');

// No assertions are made in this test
context('When config file is found', () => {
beforeAll(() => {
jest.resetModules();
// Mock the Ajv module
// This is not hoisted. It runs exactly where it's placed in the code. This allows you to change mocks dynamically during a test.
// Can be scoped to individual tests or blocks of code. More commonly used with a factory function to create complex mocks.
jest.doMock('ajv', () => ({
__esModule: true,
// Create a mock Ajv class
default: class MockAjv {
compile() {
// Return a function that logs the data and always returns true
return (data: any) => {
console.log('Validating data:', data);
return true; // Always return true, this will be overridden by the schema validation in the tests below
};
}
errorsText() {
// Always return 'Mock error' when errorsText is called
return 'Mock error';
}
}
}));
});

it('should parse YAML config file', () => {
// Mock fs.existsSync to return true
(fs.existsSync as jest.Mock).mockReturnValue(true);
// Mock fs.readFileSync to return 'yaml content'
(fs.readFileSync as jest.Mock).mockReturnValue('yaml content');
// Mock yaml.load to return mockConfig
(yaml.load as jest.Mock).mockReturnValue(mockConfig);

// Call the function we're testing
const result = loadConfig('/repo/path');

// Assert that the result matches our mockConfig
expect(result).toEqual(mockConfig);
// Check that yaml.load was called with 'yaml content'
expect(yaml.load).toHaveBeenCalledWith('yaml content');
});

it('should validate config against schema', () => {
// Mock fs.existsSync to return true
(fs.existsSync as jest.Mock).mockReturnValue(true);
// Mock fs.readFileSync to return 'yaml content'
(fs.readFileSync as jest.Mock).mockReturnValue('yaml content');
// Mock yaml.load to return mockConfig
(yaml.load as jest.Mock).mockReturnValue(mockConfig);

// Call the function we're testing
loadConfig('/repo/path');

// No assertions are made in this test
});
});

it('should throw an error if config is invalid', () => {
// Reset all module mocks to ensure we're using fresh mocks
jest.resetModules();

// Re-mock fs module
jest.doMock('fs', () => ({
existsSync: jest.fn().mockReturnValue(true),
readFileSync: jest.fn().mockReturnValue('yaml content'),
}));
// Re-mock path module
jest.doMock('path', () => ({
join: jest.fn((...args) => args.join('/')),
}));
// Re-mock js-yaml module
jest.doMock('js-yaml', () => ({
load: jest.fn().mockReturnValue({ some: 'config' }),
}));

// Mock Ajv to return false for validation and a specific error message
jest.doMock('ajv', () => ({
__esModule: true,
default: class MockAjv {
compile() {
return () => false;
}
errorsText() {
return 'Validation error';
context('When config file does not conform to schema', () => {
beforeAll(() => {
// Reset all module mocks to ensure we're using fresh mocks
jest.resetModules();

// Re-mock fs module
jest.doMock('fs', () => ({
existsSync: jest.fn().mockReturnValue(true),
readFileSync: jest.fn().mockReturnValue('yaml content'),
}));
// Re-mock path module
jest.doMock('path', () => ({
join: jest.fn((...args) => args.join('/')),
}));
// Re-mock js-yaml module
jest.doMock('js-yaml', () => ({
load: jest.fn().mockReturnValue({ some: 'config' }),
}));

// Mock Ajv to return false for validation and a specific error message
jest.doMock('ajv', () => ({
__esModule: true,
default: class MockAjv {
compile() {
return () => false;
}
errorsText() {
return 'Validation error';
}
}
}
}));
}));
});

// Re-import loadConfig to use the new mocks
const { loadConfig } = require('../src/config');
it('should throw an error if config is invalid', () => {
// Re-import loadConfig to use the new mocks
const { loadConfig } = require('../src/config');

// Assert that calling loadConfig throws an error with the correct message
expect(() => loadConfig('/repo/path')).toThrow('Invalid config: Validation error');
// Assert that calling loadConfig throws an error with the correct message
expect(() => loadConfig('/repo/path')).toThrow('Invalid config: Validation error');
});
});
});

0 comments on commit 5fb7b3c

Please sign in to comment.