npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

eslint-plugin-getdeps

v1.8.1

Published

ESLint rules for enforcing the getDeps dependency injection pattern

Readme

eslint-plugin-getdeps

Elegant dependency injection for modern JavaScript
Because testing shouldn't be a battle against your own code

ESLint rules for enforcing the getDeps dependency injection pattern. This pattern transforms your code from a testing nightmare into a testing dream, using dependency injection with default parameters to create clean, maintainable, and thoroughly testable code.

Installation

npm install --save-dev eslint-plugin-getdeps

Usage

Quick Start (Recommended)

For most projects, use the recommended configuration:

// eslint.config.js
import getdeps from 'eslint-plugin-getdeps';

export default [
  getdeps.configs.recommended,
  // your other config...
];

Advanced Usage

For maximum enforcement, use the strict configuration:

// eslint.config.js
import getdeps from 'eslint-plugin-getdeps';

export default [
  getdeps.configs.strict,
  // your other config...
];

Manual Configuration

For custom setups, configure rules individually:

// eslint.config.js
import getdeps from 'eslint-plugin-getdeps';

export default [
  {
    plugins: {
      getdeps,
    },
    rules: {
      'getdeps/no-unused-getdeps-props': 'error',
      'getdeps/no-missing-getdeps-props': 'error',
      'getdeps/no-nested-getdeps-calls': 'error',
      'getdeps/no-getdeps-as-argument': 'error',
      'getdeps/no-export-getdeps': 'error',
      'getdeps/no-unnecessary-getdeps': 'error',
      'getdeps/imports-in-getdeps': 'error',
      'getdeps/getdeps-shadowing': 'error',
      'getdeps/no-multiple-getdeps': 'error',
      'getdeps/no-globals-in-getdeps': 'error',
    },
  },
];

Configuration Differences

| Configuration | no-unnecessary-getdeps | imports-in-getdeps | no-globals-in-getdeps | Purpose | |---------------|-------------------------|---------------------|------------------------|---------| | recommended | warn | off | error | Gentle introduction with warnings | | strict | error | always | error | Maximum enforcement of all patterns | | manual | Your choice | Your choice | Your choice | Full customization |

Configuration Options

All rules support configurable function names to match your team's conventions:

// eslint.config.js
import getdeps from 'eslint-plugin-getdeps';

export default [
  {
    plugins: {
      getdeps,
    },
    rules: {
      'getdeps/no-unused-getdeps-props': ['error', {
        functionName: 'getDependencies', // Custom function name (default: 'getDeps')
        callName: 'deps'                 // Custom call name (default: '_getDeps')
      }],
      'getdeps/no-missing-getdeps-props': ['error', {
        functionName: 'getDependencies', // Custom function name (default: 'getDeps')
        callName: 'deps'                 // Custom call name (default: '_getDeps')
      }],
      'getdeps/no-nested-getdeps-calls': ['error', {
        functionName: 'getDependencies',
        callName: 'deps'
      }],
      'getdeps/no-getdeps-as-argument': ['error', {
        functionName: 'getDependencies',
        callName: 'deps',
        allowInTests: true,              // Allow in test files (default: true)
        testFilePatterns: [              // Custom test file patterns (default: see below)
          '**/*.test.js',
          '**/*.spec.js',
          '**/__tests__/**/*.js',
          '**/test/**/*.js'
        ]
      }],
      'getdeps/no-export-getdeps': ['error', {
        functionName: 'getDependencies',
        callName: 'deps',
        allowInTests: false,
        allowInHelpers: false
      }],
      'getdeps/no-unnecessary-getdeps': ['error', {
        functionName: 'getDependencies'
      }],
    },
  },
];

Configuration Options:

  • functionName: The name of your dependency function (default: 'getDeps')
  • callName: The name(s) used to call the function in your code (default: ['getDeps', '_getDeps', 'getDependencies', '_getDependencies'])
  • allowInTests: Whether to allow passing getDeps as arguments in test files (default: true)
  • testFilePatterns: Array of glob patterns to identify test files (default: ['**/*.test.js', '**/*.spec.js', '**/__tests__/**/*.js', '**/test/**/*.js'])

Important Note: By default, the plugin supports multiple call names: getDeps, _getDeps, getDependencies, and _getDependencies. This means you can use any of these names without additional configuration. If you want to use a different name, you can configure the callName option.

Example with custom names:

// Using custom function names
function getDependencies() {
  return { logger: console.log };
}

function myFunction(deps = getDependencies) {
  const { logger } = deps(); // ✅ Works with custom names
}

Example: Using getDeps() directly (fixes the bug report issue):

// If you want to call getDeps() directly instead of _getDeps()
// eslint.config.js
export default [
  {
    plugins: { getdeps },
    rules: {
      'getdeps/no-unused-getdeps-props': ['error', {
        functionName: 'getDeps',
        callName: 'getDeps',  // ✅ Configure to use getDeps() directly
      }],
      'getdeps/no-nested-getdeps-calls': ['error', {
        functionName: 'getDeps',
        callName: 'getDeps',
      }],
      'getdeps/no-getdeps-as-argument': ['error', {
        functionName: 'getDeps',
        callName: 'getDeps',
      }],
    },
  },
];

// Now this works without false positives:
function getDeps() {
  return {
    logger: console.log,
    config: { baseUrl: 'https://api.example.com' }
  };
}

function functionA() {
  const { logger } = getDeps();  // ✅ No false positive
  logger.info('Hello from functionA');
}

function functionB() {
  const { config } = getDeps();  // ✅ No false positive
  console.log('Base URL:', config.baseUrl);
}

Example with test file exceptions:

// eslint.config.js - Allow passing getDeps in test files
export default [
  {
    rules: {
      'getdeps/no-getdeps-as-argument': ['error', {
        allowInTests: true, // ✅ Allows passing getDeps in test files
        testFilePatterns: [
          '**/*.test.js',
          '**/*.spec.js',
          '**/tests/**/*.js',  // Custom test directory
          '**/e2e/**/*.js'     // End-to-end tests
        ]
      }],
    },
  },
];

// ✅ This is now allowed in test files
// test/user.test.js
function testUserFunction() {
  const mockDeps = { logger: jest.fn() };
  const result = userFunction('123', () => mockDeps); // ✅ No error in test files
}

Example with globals protection:

// eslint.config.js - Protect against global API usage
export default [
  {
    rules: {
      'getdeps/no-globals-in-getdeps': ['error', {
        allowInTests: true,           // Allow globals in test files
        ignoreGlobals: ['process'],   // Ignore specific globals
        testFilePatterns: [
          '**/*.test.js',
          '**/*.spec.js',
          '**/__tests__/**/*'
        ]
      }],
    },
  },
];

// ❌ This will be flagged in source files
// src/api.js
function getDeps() {
  return {
    window: global.window,    // ❌ Error - use Jest mocking instead
    fetch: global.fetch       // ❌ Error - use Jest mocking instead
  };
}

// ✅ This is allowed in test files
// test/api.test.js
function getDeps() {
  return {
    window: global.window,    // ✅ OK in test files when allowInTests: true
    fetch: global.fetch       // ✅ OK in test files when allowInTests: true
  };
}

Troubleshooting

Common Issues

False Positives with no-unused-getdeps-props

Problem: The rule reports properties as unused even when they are used through destructuring.

Example:

function getDeps() {
  return {
    logger: console.log,
    config: { baseUrl: 'https://api.example.com' }
  };
}

function functionA() {
  const { logger } = getDeps();  // ❌ False positive: logger reported as unused
  logger.info('Hello');
}

Solution: The plugin now supports multiple call names by default, including getDeps(). If you're still experiencing issues, you can configure the callName option explicitly:

// eslint.config.js
export default [
  {
    plugins: { getdeps },
    rules: {
      'getdeps/no-unused-getdeps-props': ['error', {
        callName: 'getDeps',  // ✅ Match your actual function call
      }],
    },
  },
];

Why this happens: By default, the plugin supports multiple call names including getDeps(), so this issue should not occur with the current version. If you're using a custom call name that's not in the default list, you need to configure it explicitly.

Rules

no-unused-getdeps-props

Detects unused properties in getDeps function returns.

❌ Bad:

function getDeps() {
  return {
    logger: console.log,
    helper: () => {},
    unusedProperty: 'this is never used', // ❌ Error
  };
}

function someFunction() {
  const { logger } = _getDeps(); // Only logger is used
}

✅ Good:

function getDeps() {
  return {
    logger: console.log,
    helper: () => {},
  };
}

function someFunction() {
  const { logger, helper } = _getDeps(); // All properties are used
}

no-missing-getdeps-props

Detects missing properties in getDeps function returns that are requested in destructuring patterns. This provides bidirectional protection from missing dependencies.

❌ Bad:

function getDeps() {
  return {
    logger: console.log,
    // Missing 'api' property that is requested in destructuring
  };
}

function someFunction() {
  const { logger, api } = _getDeps(); // ❌ Error - 'api' is not returned by getDeps
  logger('hello');
  api.get('/users');
}

✅ Good:

function getDeps() {
  return {
    logger: console.log,
    api: fetch, // ✅ All requested properties are returned
  };
}

function someFunction() {
  const { logger, api } = _getDeps(); // ✅ All properties exist
  logger('hello');
  api.get('/users');
}

no-nested-getdeps-calls

Ensures _getDeps() calls are at the top level of functions.

❌ Bad:

function badFunction() {
  if (condition) {
    const { logger } = _getDeps(); // ❌ Error - nested in if
  }
  
  return () => {
    const { logger } = _getDeps(); // ❌ Error - nested in arrow function
  };
}

✅ Good:

function goodFunction() {
  const { logger } = _getDeps(); // ✅ OK - at top level
  
  if (condition) {
    logger('hello');
  }
  
  return () => {
    logger('world');
  };
}

no-getdeps-as-argument

Prevents getDeps from being passed as arguments - use default parameters instead. Note: This rule allows passing getDeps in test files by default (configurable via allowInTests and testFilePatterns options).

❌ Bad:

function badFunction() {
  const handler = createHandler(getDeps); // ❌ Error - passing as argument
}

function badFunction2(getDeps) { // ❌ Error - required parameter
  const { logger } = getDeps();
}

const badFunction3 = (getDeps) => { // ❌ Error - required parameter
  const { logger } = getDeps();
};

✅ Good:

function goodFunction(_getDeps = getDeps) { // ✅ OK - default parameter
  const { logger } = _getDeps();
}

const goodFunction2 = (_getDeps = getDeps) => { // ✅ OK - default parameter
  const { logger } = _getDeps();
};

✅ Allowed in test files:

// test/user.test.js
function testUserFunction() {
  const mockDeps = { logger: jest.fn() };
  const result = userFunction('123', () => mockDeps); // ✅ OK - test file exception
}

no-unnecessary-getdeps

Detects when getDeps is used for dependencies that could be mocked with traditional methods like Jest.

❌ Bad:

function getDeps() {
  return {
    // Framework dependencies that Jest handles well
    axios: require('axios'),
    lodash: require('lodash'),
    
    // Simple utility functions with no external dependencies
    add: (a, b) => a + b,
    isEven: (num) => num % 2 === 0,
  };
}

✅ Good:

function getDeps() {
  return {
    // Complex functions with external dependencies
    processUser: (userId) => {
      const user = database.getUser(userId);
      return api.updateUser(user);
    },
    
    // Functions that interact with external services
    sendEmail: (to, subject) => {
      return emailService.send({ to, subject });
    },
  };
}

// Use Jest mocks for simple dependencies
jest.mock('axios');
jest.mock('lodash');

no-export-getdeps

Prevents configured getDeps function names from being exported from modules. This ensures that getDeps functions remain internal to modules and aren't accidentally exposed as part of the public API.

❌ Bad:

function getDeps() {
  return { logger: console.log };
}

export { getDeps }; // ❌ Error - getDeps should not be exported
export default getDeps; // ❌ Error - getDeps should not be exported

✅ Good:

function getDeps() {
  return { logger: console.log };
}

function processUser(userId, deps = getDeps) {
  const { logger } = deps();
  logger('Processing user');
}

export { processUser }; // ✅ OK - export other functions, not getDeps
export default processUser; // ✅ OK - export other functions, not getDeps

Configuration options:

'getdeps/no-export-getdeps': ['error', {
  functionName: 'getDependencies', // Custom function name (default: 'getDeps')
  callName: 'deps',                // Custom call name (default: '_getDeps')
  allowInTests: false,             // Allow exports in test files (default: false)
  testFilePatterns: [              // Custom test file patterns
    '**/*.test.js',
    '**/*.spec.js'
  ],
  allowInHelpers: false,           // Allow exports in helper files (default: false)
  helperFilePatterns: [            // Custom helper file patterns
    '**/helpers/**/*',
    '**/utils/**/*'
  ]
}]

✅ Allowed in test files when configured:

// test/user.test.js
function getDeps() {
  return { logger: jest.fn() };
}

export { getDeps }; // ✅ OK - when allowInTests: true

✅ Allowed in helper files when configured:

// helpers/utils.js
function getDeps() {
  return { logger: console.log };
}

export { getDeps }; // ✅ OK - when allowInHelpers: true

imports-in-getdeps

Validates that imported modules are properly included in getDeps declarations. This rule helps ensure consistency between your imports and your dependency injection pattern.

Configuration options:

'getdeps/imports-in-getdeps': ['error', {
  functionName: 'getDeps',           // Custom function name (default: 'getDeps')
  callName: '_getDeps',              // Custom call name (default: '_getDeps')
  mode: 'always',                    // 'off' | 'always' | 'never' (default: 'always')
  ignorePatterns: ['React', 'react'], // Patterns to ignore (default: [])
  allowTypeImports: true             // Allow type imports to be excluded (default: true)
}]

Mode: 'always' (default) - All imports should be in getDeps:

❌ Bad:

import { logger } from './logger';
import axios from 'axios';

function getDeps() {
  return { logger }; // ❌ Error - axios is imported but not in getDeps
}

function someFunction() {
  const { logger } = _getDeps();
  axios.get('/api/users'); // Using axios but it's not in getDeps
}

✅ Good:

import { logger } from './logger';
import axios from 'axios';

function getDeps() {
  return { logger, axios }; // ✅ All imports are included
}

function someFunction() {
  const { logger, axios } = _getDeps();
  axios.get('/api/users');
}

Mode: 'never' - No imports should be in getDeps:

❌ Bad:

import { logger } from './logger';

function getDeps() {
  return { logger }; // ❌ Error - logger is imported and in getDeps
}

function someFunction() {
  const { logger } = _getDeps();
}

✅ Good:

import { logger } from './logger';

function getDeps() {
  return { database, api }; // ✅ No imported modules in getDeps
}

function someFunction() {
  const { database, api } = _getDeps();
  logger('Using database and api from getDeps');
}

Mode: 'off' - No validation:

✅ Good:

import { logger } from './logger';
import axios from 'axios';

function getDeps() {
  return { logger }; // ✅ No validation when mode is 'off'
}

function someFunction() {
  const { logger } = _getDeps();
  axios.get('/api/users');
}

Ignoring patterns:

'getdeps/imports-in-getdeps': ['error', {
  mode: 'always',
  ignorePatterns: ['React', 'react', 'lodash'] // These imports won't be validated
}]

getdeps-shadowing

Enforces consistent naming patterns for destructured properties to avoid shadowing and make dependency injection more explicit.

Configuration options:

'getdeps/getdeps-shadowing': ['error', {
  functionName: 'getDeps',           // Custom function name (default: 'getDeps')
  callName: '_getDeps',              // Custom call name (default: '_getDeps')
  mode: 'all',                       // 'all' | 'none' (default: 'all')
  prefix: '_',                       // Prefix to use (default: '_')
  ignorePatterns: ['api', 'config']  // Patterns to ignore (default: [])
}]

Mode: 'all' (default) - All destructured properties should use prefix aliases:

❌ Bad:

function getDeps() {
  return { logger, api };
}

function someFunction() {
  const { logger, api } = _getDeps(); // ❌ Error - should use prefix aliases to avoid shadowing
}

✅ Good:

function getDeps() {
  return { logger, api };
}

function someFunction() {
  const { logger: _logger, api: _api } = _getDeps(); // ✅ Uses underscore aliases to avoid shadowing
}

Custom prefix example:

// Using 'mock' prefix
'getdeps/getdeps-shadowing': ['error', { mode: 'all', prefix: 'mock' }]

function getDeps() {
  return { logger, api };
}

function someFunction() {
  const { logger: mockLogger, api: mockApi } = _getDeps(); // ✅ Uses 'mock' prefix aliases
}

Mode: 'none' - No destructured properties should use prefix aliases:

❌ Bad:

function getDeps() {
  return { logger, api };
}

function someFunction() {
  const { logger: _logger, api: _api } = _getDeps(); // ❌ Error - should not use prefix aliases
}

✅ Good:

function getDeps() {
  return { logger, api };
}

function someFunction() {
  const { logger, api } = _getDeps(); // ✅ No prefix aliases
}

Ignoring patterns:

'getdeps/getdeps-shadowing': ['error', {
  mode: 'all',
  prefix: '_',
  ignorePatterns: ['api'] // Properties containing 'api' won't be validated
}]

Common prefix examples:

// Underscore prefix (default)
'getdeps/getdeps-shadowing': ['error', { mode: 'all', prefix: '_' }]
// Result: { logger: _logger, api: _api }

// Mock prefix
'getdeps/getdeps-shadowing': ['error', { mode: 'all', prefix: 'mock' }]
// Result: { logger: mockLogger, api: mockApi }

// Test prefix
'getdeps/getdeps-shadowing': ['error', { mode: 'all', prefix: 'test' }]
// Result: { logger: testLogger, api: testApi }

// No prefix
'getdeps/getdeps-shadowing': ['error', { mode: 'none' }]
// Result: { logger, api }

no-multiple-getdeps

Prevents multiple getDeps function declarations within the same module to maintain clarity and avoid confusion.

Why this rule exists: According to the getDeps pattern, there should only be one getDeps function per module. Multiple declarations can lead to:

  • Confusion about which implementation is being used
  • Inconsistent dependency injection across the module
  • Maintenance issues when updating dependencies

Rule: 'getdeps/no-multiple-getdeps': 'error'

❌ Incorrect:

function getDeps() {
  return { logger: console.log };
}

// Later in the same file - this will be flagged
getDeps = () => {
  return { api: fetch };
};

✅ Correct:

// Only one getDeps function per module
function getDeps() {
  return { 
    logger: console.log,
    api: fetch 
  };
}

// Use the single getDeps function consistently
function myFunction(_getDeps = getDeps) {
  const { logger, api } = _getDeps();
  // ...
}

Configuration options:

'getdeps/no-multiple-getdeps': ['error', {
  functionName: 'getDeps' // Custom function name to check (default: 'getDeps')
}]

Example with custom function name:

// Will detect multiple 'getDependencies' declarations
'getdeps/no-multiple-getdeps': ['error', { functionName: 'getDependencies' }]

no-globals-in-getdeps

Prevents global APIs from being used in getDeps declarations or destructuring, encouraging the use of Jest mocking instead.

Why this rule exists: Global APIs (like window, document, console, fetch, etc.) should be mocked using Jest rather than injected through getDeps. This ensures:

  • Better test isolation
  • Consistent mocking across your test suite
  • Proper handling of browser and Node.js globals
  • Cleaner dependency management

Rule: 'getdeps/no-globals-in-getdeps': 'error'

❌ Incorrect:

function getDeps() {
  return {
    window: global.window,        // ❌ Global API
    document: global.document,    // ❌ Global API
    fetch: global.fetch,          // ❌ Global API
    console: global.console       // ❌ Global API
  };
}

function myFunction(_getDeps = getDeps) {
  const { window, document } = _getDeps(); // ❌ Destructuring globals
}

✅ Correct:

// Use Jest mocking for global APIs
jest.mock('global', () => ({
  window: mockWindow,
  document: mockDocument,
  fetch: mockFetch,
  console: mockConsole
}));

function getDeps() {
  return {
    api: require('./api'),        // ✅ Local dependency
    utils: require('./utils'),    // ✅ Local dependency
    config: require('./config')   // ✅ Local dependency
  };
}

function myFunction(_getDeps = getDeps) {
  const { api, utils } = _getDeps(); // ✅ Only local dependencies
}

Configuration options:

'getdeps/no-globals-in-getdeps': ['error', {
  functionName: 'getDeps',           // Custom function name (default: 'getDeps')
  callName: '_getDeps',              // Custom call name (default: '_getDeps')
  allowInTests: false,               // Allow globals in test files (default: false)
  testFilePatterns: [                // Test file patterns (default: ['**/*.test.*'])
    '**/*.test.js',
    '**/*.spec.js',
    '**/__tests__/**/*'
  ],
  ignoreGlobals: ['customGlobal']    // Globals to ignore (default: [])
}]

Example with custom configuration:

// Allow globals in test files and ignore specific globals
'getdeps/no-globals-in-getdeps': ['error', {
  allowInTests: true,
  ignoreGlobals: ['process', 'Buffer']
}]

Resolving Lint Errors

When you encounter lint errors from this plugin, here are quick links to help you understand and fix them:

Common Error Solutions

  • no-unused-getdeps-props: Remove unused properties from your getDeps() return object or use all properties in your destructuring
  • no-missing-getdeps-props: Add missing properties to your getDeps() return object that are requested in destructuring patterns
  • no-nested-getdeps-calls: Move _getDeps() calls to the top level of your function, outside of any conditional blocks or nested functions
  • no-getdeps-as-argument: Use default parameters instead of passing getDeps as a function argument
  • no-unnecessary-getdeps: Use Jest mocks for framework dependencies and simple utility functions instead of getDeps
  • imports-in-getdeps: Ensure imported modules are properly included in getDeps declarations or configure the rule mode
  • getdeps-shadowing: Use consistent naming patterns for destructured properties to avoid shadowing
  • no-multiple-getdeps: Maintain only one getDeps function declaration per module
  • no-globals-in-getdeps: Use Jest mocking for global APIs instead of injecting them through getDeps

Quick Fix Examples

Error: "getDeps should not be passed as an argument"

// ❌ Before
function myFunction(deps) {
  const { logger } = deps();
}

// ✅ After  
function myFunction(_getDeps = getDeps) {
  const { logger } = _getDeps();
}

Error: "getDeps call should be at top level"

// ❌ Before
function myFunction() {
  if (condition) {
    const { logger } = _getDeps();
  }
}

// ✅ After
function myFunction() {
  const { logger } = _getDeps();
  if (condition) {
    logger('hello');
  }
}

Error: "getDeps destructuring requests property that is not returned"

// ❌ Before
function getDeps() {
  return {
    logger: console.log,
    // Missing 'api' property
  };
}

function myFunction() {
  const { logger, api } = _getDeps(); // Error: 'api' not returned
}

// ✅ After
function getDeps() {
  return {
    logger: console.log,
    api: fetch, // Add the missing property
  };
}

function myFunction() {
  const { logger, api } = _getDeps(); // Now works
}

Error: "Unused property in getDeps return"

// ❌ Before
function getDeps() {
  return { logger: console.log, unused: 'never used' };
}

// ✅ After
function getDeps() {
  return { logger: console.log };
}

Error: "getDeps returns framework dependency that can be mocked with Jest"

// ❌ Before
function getDeps() {
  return {
    axios: require('axios'),
    add: (a, b) => a + b
  };
}

// ✅ After
function getDeps() {
  return {
    processUser: (userId) => {
      const user = database.getUser(userId);
      return api.updateUser(user);
    }
  };
}

// Use Jest mocks for simple dependencies
jest.mock('axios');

Why the getDeps Pattern?

Ah, the age-old question: "Why another dependency injection pattern?"

The getDeps pattern doesn't just solve problems—it transforms your development experience. While other DI solutions feel like wearing a suit to a beach party, getDeps feels like the perfect pair of jeans: comfortable, practical, and always appropriate.

The Problems We're Solving

  1. Testing Nightmares: Functions with hardcoded dependencies are like trying to test a black box with no doors
  2. Tight Coupling: Direct imports create code that's as flexible as a concrete block
  3. Integration Testing Complexity: Mixing real and mock dependencies shouldn't require a PhD in testing theory
  4. Dependency Pollution: Unused dependencies are like leaving empty boxes in your attic—they just take up space

Before getDeps (The Dark Ages)

// ❌ Hard to test - dependencies are baked in like concrete
import { logger } from './logger';
import { database } from './database';
import { api } from './api';

function processUser(userId) {
  logger(`Processing user ${userId}`);
  const user = database.getUser(userId);
  return api.updateUser(user);
}

// Testing is painful - you need to mock modules
jest.mock('./logger');
jest.mock('./database');
jest.mock('./api');
// ... lots of setup code that makes you question your career choices

After getDeps (The Enlightenment)

// ✅ Easy to test - dependencies are injected with elegance
function getDeps() {
  return {
    logger: console.log,
    database: getDatabase(),
    api: getApiClient(),
  };
}

function processUser(userId, _getDeps = getDeps) {
  const { logger, database, api } = _getDeps();
  
  logger(`Processing user ${userId}`);
  const user = database.getUser(userId);
  return api.updateUser(user);
}

// Testing is trivial - like butter on warm toast
function testProcessUser() {
  const mockDeps = {
    logger: jest.fn(),
    database: { getUser: jest.fn().mockReturnValue({ id: '123' }) },
    api: { updateUser: jest.fn().mockReturnValue({ success: true }) },
  };
  
  const result = processUser('123', () => mockDeps);
  expect(result).toEqual({ success: true });
}

Advanced Testing Patterns

Mixed Mocking & Integration Testing

Here's where getDeps really shines—the ability to mix real and mock dependencies like a master chef combining ingredients. This is the testing equivalent of having your cake and eating it too.

function getDeps() {
  return {
    logger: console.log,
    database: getDatabase(), // Real database
    api: getApiClient(),     // Real API
    cache: getCache(),       // Real cache
    analytics: getAnalytics(), // Real analytics
  };
}

function processUser(userId, _getDeps = getDeps) {
  const { logger, database, api, cache, analytics } = _getDeps();
  
  logger(`Processing user ${userId}`);
  const user = database.getUser(userId);
  const result = api.updateUser(user);
  cache.set(`user:${userId}`, result);
  analytics.track('user_updated', { userId });
  
  return result;
}

// Integration test: Real database + API, mock analytics
function testProcessUserIntegration() {
  const mixedDeps = {
    logger: jest.fn(),
    database: getDatabase(), // Real database
    api: getApiClient(),     // Real API  
    cache: getCache(),       // Real cache
    analytics: { track: jest.fn() }, // Mock analytics
  };
  
  const result = processUser('123', () => mixedDeps);
  expect(result).toBeDefined();
  expect(mixedDeps.analytics.track).toHaveBeenCalledWith('user_updated', { userId: '123' });
}

Testing Complex Async Patterns

function getDeps() {
  return {
    logger: console.log,
    database: getDatabase(),
    queue: getQueue(),
    email: getEmailService(),
  };
}

async function processOrder(orderId, _getDeps = getDeps) {
  const { logger, database, queue, email } = _getDeps();
  
  logger(`Processing order ${orderId}`);
  
  // Complex async workflow
  const order = await database.getOrder(orderId);
  await queue.add('process-payment', { orderId });
  await email.sendOrderConfirmation(order.email);
  
  return { success: true, orderId };
}

// Test complex async patterns easily
async function testProcessOrder() {
  const mockDeps = {
    logger: jest.fn(),
    database: { 
      getOrder: jest.fn().mockResolvedValue({ id: '123', email: '[email protected]' }) 
    },
    queue: { 
      add: jest.fn().mockResolvedValue(true) 
    },
    email: { 
      sendOrderConfirmation: jest.fn().mockResolvedValue(true) 
    },
  };
  
  const result = await processOrder('123', () => mockDeps);
  expect(result).toEqual({ success: true, orderId: '123' });
  expect(mockDeps.queue.add).toHaveBeenCalledWith('process-payment', { orderId: '123' });
}

Common Questions & Criticisms

We've heard them all. Here are the most common questions and our thoughtful responses.

Q: "Why not just use a dependency injection container?"

A: Ah, the classic DI container question! While DI containers are like Swiss Army knives—powerful but often overkill—getDeps is more like a perfectly crafted chef's knife: simple, elegant, and gets the job done beautifully.

getDeps is:

  • Framework agnostic - works with any JavaScript environment (no vendor lock-in)
  • Zero configuration - no setup required (because life is complicated enough)
  • Explicit dependencies - you can see exactly what each function needs (no hidden surprises)
  • Simple to understand - no magic or hidden behavior (what you see is what you get)

Q: "Default parameters seem hacky. Why not constructor injection?"

A: "Hacky" is such a strong word! Let's call it "pragmatically elegant." Constructor injection is wonderful for classes, but most modern JavaScript code is functional. getDeps embraces this reality with style.

getDeps:

  • Works with functions - no need to create classes just for DI (because not everything needs to be a class)
  • Maintains functional purity - no side effects or state (pure as the driven snow)
  • Follows JavaScript conventions - default parameters are a standard feature (we're not reinventing the wheel)
  • Reduces boilerplate - no need for constructors or factory functions (less code, more joy)

Q: "Won't this create too many parameters in my functions?"

A: Quite the opposite! The _getDeps parameter is the only additional parameter you need. It's like having a Swiss Army knife instead of carrying around a toolbox.

Compare:

// ❌ Traditional approach - multiple parameters (parameter soup)
function processUser(userId, logger, database, api, cache, analytics) {
  // ... implementation
}

// ✅ getDeps approach - single additional parameter (elegant simplicity)
function processUser(userId, _getDeps = getDeps) {
  const { logger, database, api, cache, analytics } = _getDeps();
  // ... implementation
}

Q: "What about performance? Creating objects on every call?"

A: Ah, the performance question—every developer's favorite concern! Modern JavaScript engines are like Olympic athletes: incredibly optimized for object creation. The performance impact is so negligible it's like worrying about the weight of a feather on a sumo wrestler.

  • Microseconds vs milliseconds - object creation is extremely fast (faster than you can blink)
  • Memory pressure is minimal - objects are small and short-lived (like mayflies, but more useful)
  • Garbage collection friendly - objects are immediately eligible for cleanup (no memory leaks here)
  • Profiling shows no impact - in real applications, this is never a bottleneck (we've checked)

Q: "How is this different from just passing dependencies as parameters?"

A: Excellent question! Passing dependencies as parameters is like trying to solve a Rubik's cube with one hand tied behind your back—possible, but unnecessarily difficult.

// ❌ Parameter passing approach (the dependency soup)
function processUser(userId, logger, database, api) {
  // What if you need to add a new dependency?
  // You have to update every call site (maintenance nightmare)
}

// ❌ Object parameter approach (the mystery box)
function processUser(userId, deps) {
  // No type safety, no clear contract
  // Easy to forget required dependencies (like forgetting your keys)
}

// ✅ getDeps approach (the elegant solution)
function processUser(userId, _getDeps = getDeps) {
  const { logger, database, api } = _getDeps();
  // Clear contract, type-safe, easy to extend (like a well-designed API)
}

The getDeps Pattern

In essence, the getDeps pattern is a dependency injection technique that transforms your code from a testing nightmare into a testing dream.

  1. Uses default parameters for dependency injection (elegant and simple)
  2. Keeps dependencies at the top level of functions (clean and organized)
  3. Makes testing easier by allowing dependency mocking (because testing should be joyful)
  4. Prevents unused dependencies from being returned (no clutter, no waste)
  5. Enables mixed testing strategies - unit, integration, and end-to-end (testing flexibility)
  6. Maintains code clarity with explicit dependency contracts (what you see is what you get)

When NOT to Use getDeps

As with any good thing in life, moderation is key. Here's when getDeps might be overkill:

Framework-Level Dependencies

❌ Don't use getDeps for:

// Framework modules that Jest handles beautifully
import React from 'react';
import { render, screen } from '@testing-library/react';
import { useRouter } from 'next/router';

// Third-party libraries with good mocking support
import axios from 'axios';
import lodash from 'lodash';
import moment from 'moment';

✅ Use Jest mocks instead:

// Jest handles these elegantly
jest.mock('axios');
jest.mock('lodash');
jest.mock('next/router', () => ({
  useRouter: () => ({ push: jest.fn() })
}));

Simple Utility Functions

❌ Overkill:

// This is just a pure function - no need for DI
function formatCurrency(amount, _getDeps = getDeps) {
  const { currencyFormatter } = _getDeps();
  return currencyFormatter.format(amount);
}

✅ Better approach:

// Simple, pure, and testable
function formatCurrency(amount, locale = 'en-US') {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: 'USD'
  }).format(amount);
}

When You're Just Passing Through

❌ Unnecessary complexity:

function processData(data, _getDeps = getDeps) {
  const { logger } = _getDeps();
  logger('Processing data');
  return data.map(item => item.toUpperCase()); // No external dependencies!
}

✅ Keep it simple:

function processData(data) {
  console.log('Processing data'); // Or use a simple logger
  return data.map(item => item.toUpperCase());
}

The Golden Rule

Use getDeps when you have:

  • External service calls (APIs, databases, file systems)
  • Complex business logic with multiple dependencies
  • Functions that need different behavior in tests
  • Integration points that benefit from mixed real/mock testing

Skip getDeps when you have:

  • Framework dependencies (React, Vue, Express)
  • Pure utility functions with no external dependencies
  • Simple data transformations without side effects
  • Third-party libraries that Jest mocks handle well

Remember: getDeps is a Tool, Not a Religion

The goal is better code, not more getDeps. Sometimes the simplest solution is the best solution.

Contributing

We love contributors! Here's how you can help make getDeps even better:

  1. Fork the repository (because sharing is caring)
  2. Create a feature branch (because organization is key)
  3. Make your changes (because innovation never stops)
  4. Add tests (because quality matters)
  5. Submit a pull request (because collaboration makes us stronger)

Development Workflow

This project uses conventional commits and automatic version bumping:

Making Changes:

# Make your changes
git add .
git commit -m "feat: add new configuration option"  # Use conventional commit format

Version Bumping:

# After committing, bump version based on commit type
npm run bump

Conventional Commit Types:

  • feat: - New features (bumps minor version)
  • fix: - Bug fixes (bumps patch version)
  • docs: - Documentation changes (bumps patch version)
  • style: - Code style changes (bumps patch version)
  • refactor: - Code refactoring (bumps patch version)
  • test: - Adding tests (bumps patch version)
  • chore: - Maintenance tasks (bumps patch version)

Example Workflow:

# 1. Make changes
git add .
git commit -m "fix: resolve false positive in test files"

# 2. Bump version (automatically detects 'fix:' and bumps patch)
npm run bump

# 3. Push changes
git push

Reporting Issues

Found a bug or have a feature request? We'd love to hear from you!

License

MIT - Because we believe in freedom, both in code and in life.