Skip to content

Commit

Permalink
feat: test using mocks in Jest, as well as the nock module
Browse files Browse the repository at this point in the history
- feat: add initial handleConfigOption test
- feat!: change coverage script to test:coverage
- docs: add examples of CLI commands for testing
- docs: update CONTRIBUTING.md with watch scripts
- build: add nock devDependency & test:watch script
- fix: remove fake test file
- feat: add file_functions tests which uses jest mocks
- fix: variable names within handleConfigOption.test.js
- feat: add initial tests using 'nock' for groqConfig.test.js
- feat: add initial tests for geminiConfig.test.js - can't use nock
  • Loading branch information
peterdanwan committed Nov 11, 2024
1 parent e8e4cb8 commit bd639e6
Show file tree
Hide file tree
Showing 12 changed files with 815 additions and 19 deletions.
54 changes: 47 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,11 @@ By participating, you are expected to uphold this code. Please report unacceptab

> If you want to ask a question, we assume that you have read the available [Documentation](https://github.com/peterdanwan/gimme_readme) and [Examples](https://github.com/peterdanwan/gimme_readme/blob/main/_examples/README.md).
Before asking a question, please search for existing [Issues](https://github.com/peterdanwan/gimme_readme/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue.
Before asking a question, please search for existing [Issues](https://github.com/peterdanwan/gimme_readme/issues) that might help you and like the comment of the original issue if you find something that matches your own. In case you have found a new distinct issue, please:

If you still need to ask a question or require clarification, we recommend:

1. Opening an [Issue](https://github.com/peterdanwan/gimme_readme/issues/new).
2. Providing as much context as you can about what you're running into. (Posting code snippets, screenshots, videos etc. are welcome!).
3. Providing project and platform versions (nodejs, npm, etc), depending on what seems relevant.
1. Open an [Issue](https://github.com/peterdanwan/gimme_readme/issues/new).
2. Provide as much context as you can about what you're running into. (Posting code snippets, screenshots, videos etc. are welcome!).
3. Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant.

We will then take care of the issue as soon as possible.

Expand Down Expand Up @@ -162,7 +160,7 @@ We're excited to help you make your first code contribution! Here's a comprehens

> We maintain that it's always a best practice to _never_ work off of your `main` branch, and instead, work on a separate branch. You should do your best to ensure that your `local main` branch, and your `downstream main branch` on GitHub, is in-sync with the [upstream main branch](https://github.com/peterdanwan/gimme_readme).
2. After adding new code or modifying existing code, please try to add a `test` case for these changes (if applicable). Run the following commands to ensure your current changes are: formatted properly, have no code lint (i.e., potential for bugs), and don't break any new or existing test cases:
2. After adding new code or modifying existing code, please try to add a `test` case for these changes if applicable (read the [Running Tests Manually Section](#running-tests-manually) for more details). Run the following commands to ensure your current changes are: formatted properly, have no code lint (i.e., potential for bugs), and don't break any new or existing test cases:

```sh
# To spot any formatting issues and automatically fix them
Expand Down Expand Up @@ -196,6 +194,48 @@ We're excited to help you make your first code contribution! Here's a comprehens

> This pre-commit hook aims to ensure that your commit passes the [continuous integration tests](.github/workflows/ci.yml). If your code fails the `lint` or `test` commands, you'll notice that your commit will not go through. You'll need to address these issues _first_, and then redo your commit.
#### Running Tests Manually

Like many other `JavaScript` projects, `gimme_readme` uses [Jest](https://jestjs.io/) to `test` the current behaviour of our `source code`.
Please read more about how to use Jest for testing over [here](https://jestjs.io/docs/getting-started).

> NOTE: using `Jest` with `ES6` modules (e.g., using `import` for `ES6` vs. `require` for `CommonJS`) requires using an experimental `Node` feature,
> which is explained in-depth in this [Stack Overflow Answer](https://stackoverflow.com/questions/35756479/does-jest-support-es6-import-export).
> This affects our different `test` scripts within [package.json](./package.json) - all of them start with `node --experimental-vm-modules node_modules/jest/bin/jest.js`.
With regards to the `gimme_readme` repository, the code within the `src` folder is _tested by_ the code within the `tests` folder. Often times, when you're trying to add a new test or check if you have broken an _existing_ test, you will want to single out these tests as opposed to running _all_ tests.

Below, are several ways of running the `gimme_readme` tests from the command-line:

```sh
# Example 1: Run ALL test files
npm run test

# Example 2: Run test files within a certain folder
npm run test tests/unit/ai/models/

# Example 3: Run a singular test file
npm run test tests/unit/ai/models/geminiModels.test.js

# Example 4: Run ALL test files and get a coverage report for all files
npm run test:coverage

# Example 5: Run test files within a certain folder and get a coverage report for the selected files
npm run test:coverage tests/unit/ai/models/

# Example 6: Run a singular test file and get a coverage report
npm run test:coverage tests/unit/file_functions/_gr.test.js

# Example 7: Automatically run ALL test files when the source code changes
npm run test:watch

# Example 8: Automatically run test files within a certain folder when the relevant source code changes
npm run test:watch tests/unit/ai/models/

# Example 9: Automatically run a singular

```

#### Visual Studio Code Editor Integration

As mentioned in [Prerequisite tools](#prerequisite-tools), part of developing `gimme_readme` involves using `Visual Studio Code` as your editor. When you open the `gimme_readme` repository with `Visual Studio Code`, it should suggest that you install several extensions if they haven't been installed already. `Visual Studio Code` specific configurations can be found in the `.vscode` folder.
Expand Down
56 changes: 56 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
"type": "module",
"scripts": {
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js -c jest.config.js --runInBand",
"test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js -c jest.config.js --runInBand --coverage",
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch --",
"lint": "eslint --config eslint.config.mjs \"./src/**/*.js\" \"tests/**/*.js\"",
"format": "prettier --write \"./src/**/*.js\" \"tests/**/*.js\"",
"coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js -c jest.config.js --runInBand --coverage",
"prepare": "husky"
},
"repository": {
Expand Down Expand Up @@ -47,6 +48,7 @@
"globals": "^15.9.0",
"husky": "^9.1.6",
"jest": "^29.7.0",
"nock": "^13.5.5",
"prettier": "^3.3.3",
"supertest": "^7.0.0"
}
Expand Down
2 changes: 1 addition & 1 deletion src/ai/config/geminiConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { GoogleGenerativeAI } from '@google/generative-ai';
import getTOMLFileValues from '../../file_functions/getTOMLFileValues.js';

const toml = getTOMLFileValues();
const geminiKey = toml.api_keys.GEMINI_KEY || process.env.GEMINI_KEY;
const geminiKey = toml?.api_keys?.GEMINI_KEY || process.env.GEMINI_KEY;

// Initialize Google Generative AI client
const genAI = new GoogleGenerativeAI(geminiKey);
Expand Down
2 changes: 1 addition & 1 deletion src/ai/config/groqConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import getTOMLFileValues from '../../file_functions/getTOMLFileValues.js';
const toml = getTOMLFileValues();

// Use apiKey provided in file, otherwise fall back to environment variable
const apiKey = toml.api_keys.GROQ_KEY || process.env.GROQ_KEY;
const apiKey = toml?.api_keys?.GROQ_KEY || process.env.GROQ_KEY;
const groq = new Groq({ apiKey });

// Export function to handle Groq-specific prompting
Expand Down
135 changes: 135 additions & 0 deletions tests/unit/ai/config/geminiConfig.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// tests/unit/ai/config/geminiConfig.test.js
import { jest } from '@jest/globals';

// Mock objects
const mockGenerateContent = jest.fn();
const mockGetGenerativeModel = jest.fn(() => ({
generateContent: mockGenerateContent,
}));

// Export GoogleGenerativeAI as both default and named export
const MockGoogleGenerativeAI = jest.fn(() => ({
getGenerativeModel: mockGetGenerativeModel,
}));

// Mock the module with both default and named exports
jest.unstable_mockModule('@google/generative-ai', () => ({
default: MockGoogleGenerativeAI,
GoogleGenerativeAI: MockGoogleGenerativeAI,
}));

const apiKey = 'test-api-key';
process.env.GEMINI_KEY = apiKey;

// Import the module under test after mocking
const { promptGemini } = await import('../../../../src/ai/config/geminiConfig.js');

describe('src/ai/config/geminiConfig.js tests', () => {
const model = 'gemini-pro';

beforeAll(() => {
process.env.NODE_ENV = 'test';
});

beforeEach(() => {
jest.clearAllMocks();

// Reset mock implementations
MockGoogleGenerativeAI.mockImplementation(() => ({
getGenerativeModel: mockGetGenerativeModel,
}));

mockGetGenerativeModel.mockImplementation(() => ({
generateContent: mockGenerateContent,
}));
});

// TODO: Fix this test
// test('should return a valid response from Gemini API', async () => {
// const prompt = 'Hello, how are you?';
// const temperature = 0.7;

// mockGenerateContent.mockResolvedValue({
// response: {
// text: () => 'I am fine, thank you!',
// usageMetadata: {
// promptTokenCount: 5,
// candidatesTokenCount: 5,
// totalTokenCount: 10,
// },
// },
// });

// const result = await promptGemini(prompt, model, temperature);

// expect(result.responseText).toBe('I am fine, thank you!');
// expect(MockGoogleGenerativeAI).toHaveBeenCalledWith(apiKey); // Why isn't this called
// expect(mockGetGenerativeModel).toHaveBeenCalledWith({
// model,
// temperature,
// });
// });

test('should handle API errors gracefully', async () => {
const prompt = 'Hello, how are you?';
const temperature = 0.7;

mockGenerateContent.mockRejectedValue(new Error('Invalid request'));

await expect(promptGemini(prompt, model, temperature)).rejects.toThrow(
'Error prompting Gemini: Invalid request'
);
});

test('should use default temperature when not provided', async () => {
const prompt = 'Hello';
const defaultTemperature = 0.5;

mockGenerateContent.mockResolvedValue({
response: {
text: () => 'Hi',
usageMetadata: {
promptTokenCount: 1,
candidatesTokenCount: 1,
totalTokenCount: 2,
},
},
});

const result = await promptGemini(prompt, model);

expect(result.responseText).toBe('Hi');
expect(mockGetGenerativeModel).toHaveBeenCalledWith({
model,
temperature: defaultTemperature,
});
});

test('should handle empty response from API', async () => {
const prompt = 'Hello';

mockGenerateContent.mockResolvedValue({
response: {
text: () => '',
usageMetadata: {
promptTokenCount: 1,
candidatesTokenCount: 0,
totalTokenCount: 1,
},
},
});

const result = await promptGemini(prompt, model);
expect(result.responseText).toBe('');
});

test('should handle network errors', async () => {
const prompt = 'Hello';

mockGenerateContent.mockRejectedValue(new Error('Connection refused'));

await expect(promptGemini(prompt, model)).rejects.toThrow(
'Error prompting Gemini: Connection refused'
);
});
});
Loading

0 comments on commit bd639e6

Please sign in to comment.