Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/mempool/mempool/llms.txt

Use this file to discover all available pages before exploring further.

Mempool uses comprehensive testing strategies to ensure code quality and reliability. This guide covers unit tests, integration tests, end-to-end tests, and best practices.

Overview

Mempool testing is split between backend and frontend:

Backend Testing

  • Framework: Jest
  • Types: Unit, Integration
  • Coverage: Enabled

Frontend Testing

  • Framework: Karma/Jasmine, Cypress
  • Types: Unit, E2E
  • Browsers: Chrome, Firefox, Edge

Backend Testing

The backend uses Jest as the primary testing framework with TypeScript support.

Running Tests

Run all unit tests:
cd backend
npm test
Run specific test file:
npm test -- path/to/test.test.ts
Run tests in watch mode:
npm test -- --watch

Test Structure

Backend tests are located in:
backend/
├── src/
│   ├── __tests__/              # Unit tests
│   │   ├── api/
│   │   ├── utils/
│   │   └── *.test.ts
│   ├── __integration_tests__/  # Integration tests
│   │   └── *.integration.test.ts
│   └── __fixtures__/           # Test data
│       └── *.json
└── jest.config.js

Writing Backend Tests

// backend/src/__tests__/utils/format.test.ts
import { formatBytes, getBytesUnit } from '../../utils/format';

describe('Format Utilities', () => {
  describe('formatBytes', () => {
    it('should format bytes to KB', () => {
      expect(formatBytes(1024)).toBe('1.00 KB');
    });

    it('should format bytes to MB', () => {
      expect(formatBytes(1048576)).toBe('1.00 MB');
    });

    it('should handle zero bytes', () => {
      expect(formatBytes(0)).toBe('0 Bytes');
    });
  });

  describe('getBytesUnit', () => {
    it('should return correct unit for KB', () => {
      expect(getBytesUnit(1024)).toBe('KB');
    });
  });
});
// backend/src/__integration_tests__/blocks.integration.test.ts
import DB from '../database';
import blocks from '../api/blocks';

describe('Blocks Integration', () => {
  beforeAll(async () => {
    await DB.checkDbConnection();
  });

  afterAll(async () => {
    await DB.close();
  });

  it('should fetch block from database', async () => {
    const block = await blocks.getBlock('000000000000000000000...');
    expect(block).toBeDefined();
    expect(block.height).toBeGreaterThan(0);
  });

  it('should calculate block stats', async () => {
    const stats = await blocks.calculateBlockStats(800000);
    expect(stats.totalFees).toBeGreaterThan(0);
    expect(stats.transactionCount).toBeGreaterThan(0);
  });
});
// Mocking external dependencies
import bitcoinApi from '../api/bitcoin/bitcoin-api-factory';

jest.mock('../api/bitcoin/bitcoin-api-factory');

describe('Mempool', () => {
  beforeEach(() => {
    // Mock Bitcoin Core RPC responses
    (bitcoinApi.getBlock as jest.Mock).mockResolvedValue({
      hash: '0000000000000000000...',
      height: 800000,
      tx: ['txid1', 'txid2']
    });
  });

  it('should process block', async () => {
    const result = await processBlock('0000000000000000000...');
    expect(bitcoinApi.getBlock).toHaveBeenCalledWith('0000000000000000000...');
    expect(result.processed).toBe(true);
  });
});

Jest Configuration

The backend uses a custom Jest configuration:
// backend/jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/__tests__/**/*.test.ts'],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/__tests__/**',
    '!src/__fixtures__/**'
  ],
  coverageThreshold: {
    global: {
      branches: 50,
      functions: 50,
      lines: 50,
      statements: 50
    }
  }
};

Frontend Testing

The frontend uses Karma/Jasmine for unit tests and Cypress for end-to-end tests.

Unit Tests

cd frontend
npm test
This launches Karma and runs tests in Chrome.

End-to-End Tests

Cypress is used for comprehensive E2E testing:
Open Cypress Test Runner:
npm run cypress:open
This opens an interactive GUI where you can:
  • Select test files to run
  • Watch tests execute in real browser
  • Debug failing tests
  • See screenshots and videos

Cypress Test Structure

frontend/
├── cypress/
│   ├── e2e/                    # Test files
│   │   ├── mainnet/
│   │   │   ├── blocks.cy.ts
│   │   │   ├── transactions.cy.ts
│   │   │   └── mempool.cy.ts
│   │   ├── testnet/
│   │   ├── signet/
│   │   └── liquid/
│   ├── fixtures/               # Test data
│   ├── support/                # Helper functions
│   │   ├── commands.ts
│   │   └── e2e.ts
│   └── screenshots/            # Failure screenshots
└── cypress.config.ts

Writing Frontend Tests

// frontend/src/app/components/fee-chart/fee-chart.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FeeChartComponent } from './fee-chart.component';

describe('FeeChartComponent', () => {
  let component: FeeChartComponent;
  let fixture: ComponentFixture<FeeChartComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ FeeChartComponent ]
    })
    .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(FeeChartComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should render chart with data', () => {
    component.data = mockFeeData;
    fixture.detectChanges();
    
    const chartElement = fixture.nativeElement.querySelector('.chart');
    expect(chartElement).toBeTruthy();
  });
});
// frontend/src/app/services/api.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ApiService } from './api.service';

describe('ApiService', () => {
  let service: ApiService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [ApiService]
    });
    service = TestBed.inject(ApiService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('should fetch block by hash', () => {
    const mockBlock = { hash: '000...', height: 800000 };

    service.getBlock('000...').subscribe(block => {
      expect(block).toEqual(mockBlock);
    });

    const req = httpMock.expectOne('/api/block/000...');
    expect(req.request.method).toBe('GET');
    req.flush(mockBlock);
  });
});
// frontend/cypress/e2e/mainnet/blocks.cy.ts
describe('Block Explorer', () => {
  beforeEach(() => {
    cy.visit('/block/000000000000000000000...');
  });

  it('should display block details', () => {
    cy.get('[data-cy="block-hash"]').should('be.visible');
    cy.get('[data-cy="block-height"]').should('contain', '800000');
    cy.get('[data-cy="block-time"]').should('exist');
  });

  it('should list transactions', () => {
    cy.get('[data-cy="transaction-list"]').should('be.visible');
    cy.get('[data-cy="transaction-item"]').should('have.length.at.least', 1);
  });

  it('should navigate to transaction details', () => {
    cy.get('[data-cy="transaction-item"]').first().click();
    cy.url().should('include', '/tx/');
    cy.get('[data-cy="transaction-details"]').should('be.visible');
  });

  it('should display mining pool', () => {
    cy.get('[data-cy="mining-pool"]').should('be.visible');
  });
});
// frontend/cypress/support/commands.ts
declare global {
  namespace Cypress {
    interface Chainable {
      waitForBlockData(): Chainable<void>;
      mockWebSocket(): Chainable<void>;
    }
  }
}

Cypress.Commands.add('waitForBlockData', () => {
  cy.intercept('GET', '/api/blocks/tip/height').as('blockHeight');
  cy.wait('@blockHeight');
});

Cypress.Commands.add('mockWebSocket', () => {
  cy.intercept('ws://localhost:8999', { fixture: 'websocket-mock.json' });
});

Test Scripts Reference

Backend Scripts

{
  "scripts": {
    "test": "jest --coverage",
    "test:ci": "CI=true jest --coverage",
    "test:integration": "jest --config=jest.integration.config.ts --runInBand --forceExit",
    "test:with-db": "bash ./scripts/test-with-db.sh"
  }
}

Frontend Scripts

{
  "scripts": {
    "test": "ng test",
    "cypress:open": "cypress open",
    "cypress:run": "cypress run",
    "cypress:run:record": "cypress run --record",
    "cypress:run:ci": "npm run config:defaults:mempool && start-server-and-test serve:local-prod 4200 cypress:run:record"
  }
}

Code Quality Tools

Linting

cd backend

# Check for errors
npm run lint

# Auto-fix issues
npm run lint:fix

Code Formatting

cd backend
npm run prettier

Continuous Integration

Mempool uses GitHub Actions for automated testing.

CI Workflow

1

Lint & Type Check

- name: Lint
  run: npm run lint
2

Unit Tests

- name: Test
  run: npm test
3

Integration Tests

- name: Integration Tests
  run: npm run test:integration
4

E2E Tests (Frontend)

- name: Cypress Tests
  run: npm run cypress:run:ci
5

Coverage Upload

- name: Upload Coverage
  uses: codecov/codecov-action@v3

Testing Best Practices

Do:
it('should calculate correct fee rate for transaction', () => {
  const tx = { fee: 1000, vsize: 200 };
  expect(calculateFeeRate(tx)).toBe(5);
});
Don’t:
it('should work', () => {
  expect(true).toBe(true);
});
Each test should be independent:
describe('BlockService', () => {
  let service: BlockService;

  beforeEach(() => {
    service = new BlockService();
  });

  it('test 1', () => { /* ... */ });
  it('test 2', () => { /* ... */ });
});
Don’t rely on external services:
jest.mock('../api/bitcoin-api');

beforeEach(() => {
  (bitcoinApi.getBlock as jest.Mock)
    .mockResolvedValue(mockBlockData);
});
Cover boundary conditions:
it('should handle empty mempool', () => {
  expect(calculateFees([])).toEqual([]);
});

it('should handle single transaction', () => {
  expect(calculateFees([tx])).toEqual([expectedFee]);
});

it('should handle maximum transactions', () => {
  const largeTxSet = Array(10000).fill(tx);
  expect(calculateFees(largeTxSet)).toBeDefined();
});
Test names should be self-documenting:Good:
it('should return 404 when block hash not found')
it('should calculate median fee from sorted transactions')
Bad:
it('test1')
it('works correctly')

Debugging Tests

Backend Test Debugging

Create .vscode/launch.json:
{
  "type": "node",
  "request": "launch",
  "name": "Jest Tests",
  "program": "${workspaceFolder}/backend/node_modules/.bin/jest",
  "args": ["--runInBand"],
  "console": "integratedTerminal",
  "internalConsoleOptions": "neverOpen"
}

Cypress Test Debugging

npm run cypress:open
Use the Test Runner to:
  • Step through test execution
  • Inspect DOM at each step
  • View network requests
  • See console logs

Next Steps

Setup Guide

Set up your development environment

Contributing

Submit your first contribution

Architecture

Understand the codebase

API Documentation

Explore the REST API

Build docs developers (and LLMs) love