Skip to main content

Overview

The project uses Jest as its testing framework to validate AST transformations. Each transformer in the deobfuscation pipeline has corresponding unit tests.

Jest Configuration

The project uses a minimal Jest configuration:
jest.config.js
module.exports = {
    transform: {
        '^.+\\.jsx?$': 'babel-jest',
    },
    testEnvironment: 'node',
    clearMocks: true,
    collectCoverage: true,
    coverageDirectory: "coverage",
    coverageProvider: "v8"
};
transform
object
Uses babel-jest to transpile ES6+ syntax in test files and source code
testEnvironment
string
default:"node"
Runs tests in a Node.js environment (not browser/jsdom)
clearMocks
boolean
default:"true"
Automatically clears mock calls between tests
collectCoverage
boolean
default:"true"
Generates code coverage reports during test runs
coverageProvider
string
default:"v8"
Uses V8’s built-in coverage instrumentation for better performance

Test File Structure

Transformer tests follow the naming pattern: refactor-obfuscated-code-jscodeshift-{N}.test.js

Example Test: Void0Transformer

mitmproxy/src/javascript/refactor-obfuscated-code-jscodeshift-0.test.js
import {verifyFileExists} from "./refactor-obfuscated-code-jscodeshift-test-util.js";
import j from "jscodeshift";
import {v4 as uuidv4} from "uuid";
import {Void0Transformer} from "./refactor-obfuscated-code-jscodeshift-0";

const ast = j("var a = void 0;");

describe("Void0Transformer", () => {
    test("transform", () => {
        const transformer = createTransformer();
        transformer.transform();

        verifyFileExists(transformer.outputBaseName, 0, "var a = undefined;");
    });
});

function createTransformer() {
    return new Void0Transformer(0, ast, true, uuidv4());
}

Test Utilities

File Verification Utility

The verifyFileExists utility is used across all transformer tests:
import {verifyFileExists} from "./refactor-obfuscated-code-jscodeshift-test-util.js";
outputBaseName
string
required
Base name of the output file (typically a UUID)
stepNumber
number
required
The transformer step number (0-10)
expectedOutput
string
required
The expected transformed JavaScript code
Usage pattern:
verifyFileExists(transformer.outputBaseName, 0, "var a = undefined;");
This asserts that:
  1. The output file {outputBaseName}-0.js exists
  2. Its contents match the expected output exactly

Test Structure Pattern

All transformer tests follow a consistent structure:
import j from "jscodeshift";
import {v4 as uuidv4} from "uuid";
import {TransformerClass} from "./refactor-obfuscated-code-jscodeshift-N";
import {verifyFileExists} from "./refactor-obfuscated-code-jscodeshift-test-util.js";
const ast = j("input JavaScript code");
Creates a jscodeshift AST collection from test input code.
describe("TransformerClass", () => {
    test("transform", () => {
        // test implementation
    });
});
function createTransformer() {
    return new TransformerClass(
        stepNumber,
        ast,
        true,           // output to file
        uuidv4()        // unique output basename
    );
}
Uses UUID to ensure test output files don’t conflict.
const transformer = createTransformer();
transformer.transform();
verifyFileExists(transformer.outputBaseName, stepNumber, expectedCode);

Running Tests

Run All Tests

npm test
This executes all test files matching the pattern **/*.test.js.

Run Specific Test File

npm test -- refactor-obfuscated-code-jscodeshift-0.test.js

Run Tests in Watch Mode

npm test -- --watch
Automatically reruns tests when files change.

Run with Coverage Report

npm test -- --coverage
Generates a detailed coverage report in the coverage/ directory.

Coverage Reports

With collectCoverage: true, Jest generates coverage reports in multiple formats:
coverage/
├── lcov-report/        # HTML coverage report
   └── index.html     # Open in browser
├── lcov.info          # LCOV format
└── coverage-final.json # JSON format

Viewing Coverage

open coverage/lcov-report/index.html
Shows:
  • Statement coverage
  • Branch coverage
  • Function coverage
  • Line coverage

Test Examples

Testing Simple Transformations

const ast = j("var flag = !0;");

test("UnaryExpressionTransformer", () => {
    const transformer = new UnaryExpressionTransformer(1, ast, true, uuidv4());
    transformer.transform();
    
    verifyFileExists(
        transformer.outputBaseName,
        1,
        "var flag = true;"
    );
});

Testing Variable Renaming

const ast = j("var _0x588211 = [];");

test("RefactorVariableTransformer", () => {
    const transformer = new RefactorVariableTransformer(2, ast, true, uuidv4());
    transformer.transform();
    
    verifyFileExists(
        transformer.outputBaseName,
        2,
        "var globalState = [];"
    );
});

Testing Complex AST Manipulation

const ast = j("array['push'](item);");

test("BracketToDotNotationTransformer", () => {
    const transformer = new BracketToDotNotationTransformer(7, ast, true, uuidv4());
    transformer.transform();
    
    verifyFileExists(
        transformer.outputBaseName,
        7,
        "array.push(item);"
    );
});

Test Data Management

Using UUID for Test Isolation

Each test generates unique output files using UUIDs:
import {v4 as uuidv4} from "uuid";

const outputBaseName = uuidv4(); // e.g., "a3f5e891-4c2b-4d87-8e3f-9b2c1a4d5e6f"
const transformer = new Transformer(0, ast, true, outputBaseName);
// Outputs: a3f5e891-4c2b-4d87-8e3f-9b2c1a4d5e6f-0.js
This prevents test interference when running in parallel.

Debugging Tests

Enable Debugger in Tests

test("transform", () => {
    debugger; // Breakpoint here
    const transformer = createTransformer();
    transformer.transform();
});
Run with Node inspector:
node --inspect-brk node_modules/.bin/jest --runInBand

Inspect Output Files

Since transformers write output files during tests, you can examine them:
const transformer = new Transformer(0, ast, true, "test-output");
transformer.transform();
// Check: test-output-0.js

Verbose Test Output

npm test -- --verbose
Shows individual test names and results.

Best Practices

Test Each Transformation Independently - Each transformer has its own test file that validates only that specific transformation.
Focus on the smallest code snippet that exercises the transformation:
// Good: Minimal
const ast = j("void 0");

// Avoid: Too complex
const ast = j("function foo() { var x = void 0; return x; }");
Include tests for:
  • Empty input
  • Nested structures
  • Multiple occurrences
  • Already-transformed code (idempotency)
Always use verifyFileExists to ensure:
  1. Transform executed successfully
  2. Output file was created
  3. Output matches expectations exactly

Integration Testing

While unit tests cover individual transformers, integration testing uses the full pipeline:
node mitmproxy/src/javascript/refactor-obfuscated-code-jscodeshift.js \
  test-input.js \
  test-output.js
Compare test-output.js with expected deobfuscated code.

Continuous Integration

From README.md:10-13:
A CI/CD pipeline is planned to:
  • Detect when bet365 updates their obfuscated code
  • Run the full test suite
  • Ensure deobfuscation continues to work
  • Maintain functional backup scripts

Test Coverage Goals

ComponentTarget Coverage
Transformers100%
Utilities90%+
IntegrationKey workflows
Coverage reports are generated automatically on every test run with the current configuration.

Build docs developers (and LLMs) love