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
Unit Tests
Integration Tests
With Coverage
CI Mode
Run all unit tests: Run specific test file: npm test -- path/to/test.test.ts
Run tests in watch mode: Integration tests require a running database: These tests run against a real database. Ensure your test database is configured in mempool-config.json.
Generate coverage reports: Coverage reports are generated in coverage/ directory. View HTML coverage report: open coverage/lcov-report/index.html
Run tests in CI environment: This sets CI=true and generates coverage reports.
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
Run Tests
Watch Mode
Single Run
This launches Karma and runs tests in Chrome. Tests run in watch mode by default: Files are watched for changes and tests re-run automatically. Run tests once and exit: npm test -- --watch=false --browsers=ChromeHeadless
End-to-End Tests
Cypress is used for comprehensive E2E testing:
Interactive Mode
Headless Mode
With Recording
CI Environment
Open Cypress Test Runner: 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
Run all E2E tests headlessly: Useful for CI/CD pipelines. Run tests and record to Cypress Dashboard: npm run cypress:run:record
Run complete CI test suite: This:
Configures the app for testing
Starts the dev server
Runs Cypress tests
Records results
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
package.json Scripts
Script Descriptions
{
"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"
}
}
Script Description testRun all unit tests with coverage test:ciRun tests in CI mode test:integrationRun integration tests (requires DB) test:with-dbStart test DB, run tests, cleanup
Frontend Scripts
package.json Scripts
Script Descriptions
{
"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"
}
}
Script Description testRun Karma unit tests cypress:openOpen Cypress Test Runner cypress:runRun Cypress headlessly cypress:run:recordRun and record to dashboard cypress:run:ciFull CI test suite
Linting
cd backend
# Check for errors
npm run lint
# Auto-fix issues
npm run lint:fix
cd frontend
# Check for errors
npm run lint
# Auto-fix issues
npm run lint:fix
cd backend
npm run prettier
cd frontend
npm run prettier
Continuous Integration
Mempool uses GitHub Actions for automated testing.
CI Workflow
Lint & Type Check
- name : Lint
run : npm run lint
Unit Tests
- name : Test
run : npm test
Integration Tests
- name : Integration Tests
run : npm run test:integration
E2E Tests (Frontend)
- name : Cypress Tests
run : npm run cypress:run:ci
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' , () => { /* ... */ });
});
Mock External Dependencies
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
VS Code
Node Inspector
Console Logs
Create .vscode/launch.json: {
"type" : "node" ,
"request" : "launch" ,
"name" : "Jest Tests" ,
"program" : "${workspaceFolder}/backend/node_modules/.bin/jest" ,
"args" : [ "--runInBand" ],
"console" : "integratedTerminal" ,
"internalConsoleOptions" : "neverOpen"
}
node --inspect-brk node_modules/.bin/jest --runInBand
Then open chrome://inspect in Chrome. it ( 'should debug' , () => {
console . log ( 'Debug value:' , value );
expect ( value ). toBe ( expected );
});
Cypress Test Debugging
Interactive Mode
Screenshots
Videos
Use the Test Runner to:
Step through test execution
Inspect DOM at each step
View network requests
See console logs
Cypress automatically captures screenshots on failure: frontend/cypress/screenshots/
Next Steps
Setup Guide Set up your development environment
Contributing Submit your first contribution
Architecture Understand the codebase
API Documentation Explore the REST API