Testing and Quality Assurance

1. Unit Testing Polyfill Implementation

Test Framework Features Polyfill Testing Best For
Jest Built-in assertions, mocking jsdom environment, code coverage Modern projects
Mocha Flexible, BDD/TDD style Chai assertions, Sinon mocks Node.js, flexible setups
Jasmine All-in-one framework Spies, matchers included Standalone, no dependencies
AVA Concurrent execution Fast, isolated tests Performance-focused
QUnit Simple, minimal Browser and Node jQuery projects

Example: Jest unit tests for polyfills

// array-includes.polyfill.js
if (!Array.prototype.includes) {
    Array.prototype.includes = function(searchElement, fromIndex) {
        if (this == null) {
            throw new TypeError('Array.prototype.includes called on null or undefined');
        }
        
        var O = Object(this);
        var len = parseInt(O.length) || 0;
        
        if (len === 0) {
            return false;
        }
        
        var n = parseInt(fromIndex) || 0;
        var k;
        
        if (n >= 0) {
            k = n;
        } else {
            k = len + n;
            if (k < 0) {
                k = 0;
            }
        }
        
        while (k < len) {
            var currentElement = O[k];
            if (searchElement === currentElement ||
                (searchElement !== searchElement && currentElement !== currentElement)) {
                return true;
            }
            k++;
        }
        
        return false;
    };
}

// array-includes.test.js
describe('Array.prototype.includes polyfill', () => {
    beforeAll(() => {
        // Remove native implementation to test polyfill
        delete Array.prototype.includes;
        require('./array-includes.polyfill');
    });
    
    afterAll(() => {
        // Restore native implementation
        delete Array.prototype.includes;
    });
    
    test('should find element in array', () => {
        expect([1, 2, 3].includes(2)).toBe(true);
        expect([1, 2, 3].includes(4)).toBe(false);
    });
    
    test('should handle fromIndex parameter', () => {
        expect([1, 2, 3, 2].includes(2, 2)).toBe(true);
        expect([1, 2, 3].includes(2, 2)).toBe(false);
    });
    
    test('should handle negative fromIndex', () => {
        expect([1, 2, 3].includes(3, -1)).toBe(true);
        expect([1, 2, 3].includes(2, -1)).toBe(false);
    });
    
    test('should handle NaN correctly', () => {
        expect([1, NaN, 3].includes(NaN)).toBe(true);
        expect([1, 2, 3].includes(NaN)).toBe(false);
    });
    
    test('should handle sparse arrays', () => {
        var sparseArray = [1, , 3];
        expect(sparseArray.includes(undefined)).toBe(true);
    });
    
    test('should throw TypeError on null/undefined', () => {
        expect(() => {
            Array.prototype.includes.call(null, 1);
        }).toThrow(TypeError);
        
        expect(() => {
            Array.prototype.includes.call(undefined, 1);
        }).toThrow(TypeError);
    });
    
    test('should handle array-like objects', () => {
        var arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
        expect(Array.prototype.includes.call(arrayLike, 'b')).toBe(true);
    });
    
    test('should match native behavior', () => {
        // Compare with native on arrays that support both
        var testArray = [1, 2, 3, NaN, undefined];
        var testCases = [
            [2, undefined],
            [NaN, undefined],
            [undefined, undefined],
            [2, 1],
            [2, -2]
        ];
        
        testCases.forEach(([val, fromIdx]) => {
            var polyfillResult = testArray.includes(val, fromIdx);
            // Would compare with native if available
            expect(typeof polyfillResult).toBe('boolean');
        });
    });
});

Example: Testing Promise polyfill

// promise.test.js
describe('Promise polyfill', () => {
    test('should resolve with value', (done) => {
        new Promise((resolve) => {
            resolve('success');
        }).then((value) => {
            expect(value).toBe('success');
            done();
        });
    });
    
    test('should reject with error', (done) => {
        new Promise((resolve, reject) => {
            reject(new Error('failed'));
        }).catch((error) => {
            expect(error.message).toBe('failed');
            done();
        });
    });
    
    test('should chain promises', (done) => {
        Promise.resolve(1)
            .then((val) => val + 1)
            .then((val) => val * 2)
            .then((val) => {
                expect(val).toBe(4);
                done();
            });
    });
    
    test('Promise.all should wait for all', (done) => {
        Promise.all([
            Promise.resolve(1),
            Promise.resolve(2),
            Promise.resolve(3)
        ]).then((values) => {
            expect(values).toEqual([1, 2, 3]);
            done();
        });
    });
    
    test('Promise.race should return first', (done) => {
        Promise.race([
            new Promise((resolve) => setTimeout(() => resolve(1), 100)),
            new Promise((resolve) => setTimeout(() => resolve(2), 50))
        ]).then((value) => {
            expect(value).toBe(2);
            done();
        });
    });
});
Note: Remove native implementations in tests to ensure polyfill code is actually tested. Use beforeAll/afterAll for setup and teardown.

2. Cross-browser Compatibility Testing

Tool/Service Browsers Supported Features Cost
BrowserStack 3000+ browser/device combos Live testing, screenshots, automation Paid (free tier available)
Sauce Labs 800+ browser/OS combinations Selenium, Appium, manual testing Paid
LambdaTest 3000+ browsers/devices Live, screenshot, responsive testing Paid (free tier available)
Playwright Chrome, Firefox, Safari (WebKit) Local automated testing Free
TestCafe All major browsers No WebDriver, easy setup Free

Example: Playwright cross-browser tests

// playwright.config.js
module.exports = {
    projects: [
        {
            name: 'chromium',
            use: { browserName: 'chromium' }
        },
        {
            name: 'firefox',
            use: { browserName: 'firefox' }
        },
        {
            name: 'webkit',
            use: { browserName: 'webkit' }
        }
    ]
};

// polyfills.spec.js
const { test, expect } = require('@playwright/test');

test.describe('Polyfill cross-browser tests', () => {
    test.beforeEach(async ({ page }) => {
        await page.goto('http://localhost:3000/test.html');
    });
    
    test('Array.includes should work', async ({ page }) => {
        const result = await page.evaluate(() => {
            return [1, 2, 3].includes(2);
        });
        expect(result).toBe(true);
    });
    
    test('Promise should work', async ({ page }) => {
        const result = await page.evaluate(() => {
            return new Promise((resolve) => {
                resolve('success');
            });
        });
        expect(result).toBe('success');
    });
    
    test('fetch API should work', async ({ page }) => {
        const result = await page.evaluate(async () => {
            if (typeof fetch !== 'function') {
                return 'fetch not available';
            }
            return 'fetch available';
        });
        expect(result).toBe('fetch available');
    });
    
    test('IntersectionObserver should work', async ({ page }) => {
        const result = await page.evaluate(() => {
            return typeof IntersectionObserver !== 'undefined';
        });
        expect(result).toBe(true);
    });
    
    test('Custom Elements should work', async ({ page }) => {
        const result = await page.evaluate(() => {
            return typeof customElements !== 'undefined';
        });
        expect(result).toBe(true);
    });
});

Example: BrowserStack configuration

// browserstack.config.js
exports.config = {
    user: process.env.BROWSERSTACK_USERNAME,
    key: process.env.BROWSERSTACK_ACCESS_KEY,
    
    capabilities: [
        {
            browserName: 'Chrome',
            browser_version: '91.0',
            os: 'Windows',
            os_version: '10'
        },
        {
            browserName: 'Firefox',
            browser_version: '89.0',
            os: 'Windows',
            os_version: '10'
        },
        {
            browserName: 'Safari',
            browser_version: '14.0',
            os: 'OS X',
            os_version: 'Big Sur'
        },
        {
            browserName: 'IE',
            browser_version: '11.0',
            os: 'Windows',
            os_version: '10'
        },
        {
            browserName: 'Edge',
            browser_version: '91.0',
            os: 'Windows',
            os_version: '10'
        }
    ],
    
    specs: [
        './test/polyfills/**/*.spec.js'
    ],
    
    maxInstances: 5,
    
    logLevel: 'info',
    
    baseUrl: 'http://localhost:3000',
    
    waitforTimeout: 10000
};
Note: Test on real browsers and devices, not just emulators. Focus on target browsers (IE11, older Safari) that need polyfills most.

3. Feature Parity Testing and Validation

Validation Type What to Test Method Tools
API Signature Function parameters, return type Compare with spec TypeScript, JSDoc
Behavior Parity Same results as native Side-by-side comparison Test262, Custom tests
Edge Cases Null, undefined, NaN, empty Boundary testing Property-based testing
Error Handling Same errors as native Exception testing Jest, Mocha assertions
Spec Compliance ECMAScript specification Test262 suite Test262 runner

Example: Feature parity test suite

// feature-parity.test.js
describe('Feature parity validation', () => {
    // Test native vs polyfill
    function testParity(nativeMethod, polyfillMethod, testCases) {
        testCases.forEach(({ input, description }) => {
            test(description, () => {
                var nativeResult, polyfillResult;
                var nativeError, polyfillError;
                
                // Test native
                try {
                    nativeResult = nativeMethod.apply(null, input);
                } catch (e) {
                    nativeError = e;
                }
                
                // Test polyfill
                try {
                    polyfillResult = polyfillMethod.apply(null, input);
                } catch (e) {
                    polyfillError = e;
                }
                
                // Compare results
                if (nativeError && polyfillError) {
                    expect(polyfillError.constructor).toBe(nativeError.constructor);
                    expect(polyfillError.message).toBe(nativeError.message);
                } else if (nativeError || polyfillError) {
                    fail('Error mismatch: native=' + nativeError + ', polyfill=' + polyfillError);
                } else {
                    expect(polyfillResult).toEqual(nativeResult);
                }
            });
        });
    }
    
    describe('Object.assign parity', () => {
        var nativeAssign = Object.assign.bind(Object);
        
        // Save and replace with polyfill
        var originalAssign = Object.assign;
        Object.assign = function(target, ...sources) {
            if (target == null) {
                throw new TypeError('Cannot convert undefined or null to object');
            }
            
            var to = Object(target);
            
            for (var i = 0; i < sources.length; i++) {
                var nextSource = sources[i];
                
                if (nextSource != null) {
                    for (var key in nextSource) {
                        if (Object.prototype.hasOwnProperty.call(nextSource, key)) {
                            to[key] = nextSource[key];
                        }
                    }
                }
            }
            
            return to;
        };
        
        var testCases = [
            {
                input: [{}, { a: 1 }],
                description: 'should copy properties'
            },
            {
                input: [{ a: 1 }, { a: 2, b: 3 }],
                description: 'should overwrite properties'
            },
            {
                input: [{}, null, { a: 1 }],
                description: 'should skip null sources'
            },
            {
                input: [null, { a: 1 }],
                description: 'should throw on null target'
            },
            {
                input: [undefined, { a: 1 }],
                description: 'should throw on undefined target'
            }
        ];
        
        testParity(originalAssign, Object.assign, testCases);
        
        // Restore
        Object.assign = originalAssign;
    });
    
    describe('Array.find parity', () => {
        var testArray = [1, 2, 3, 4, 5];
        
        test('should find first matching element', () => {
            var native = testArray.find(x => x > 2);
            expect(native).toBe(3);
        });
        
        test('should return undefined when not found', () => {
            var native = testArray.find(x => x > 10);
            expect(native).toBeUndefined();
        });
        
        test('should pass element, index, array to callback', () => {
            var calls = [];
            testArray.find((el, idx, arr) => {
                calls.push({ el, idx, arr });
                return false;
            });
            
            expect(calls.length).toBe(5);
            expect(calls[0]).toEqual({ el: 1, idx: 0, arr: testArray });
        });
        
        test('should use thisArg', () => {
            var context = { threshold: 3 };
            var result = testArray.find(function(x) {
                return x > this.threshold;
            }, context);
            
            expect(result).toBe(4);
        });
    });
});

Example: Property-based testing

// property-based-tests.js (using fast-check)
const fc = require('fast-check');

describe('Property-based polyfill tests', () => {
    test('Array.includes should be consistent', () => {
        fc.assert(
            fc.property(
                fc.array(fc.integer()),
                fc.integer(),
                (arr, val) => {
                    var includesResult = arr.includes(val);
                    var indexOfResult = arr.indexOf(val) !== -1;
                    
                    // includes and indexOf should agree (except for NaN)
                    if (!Number.isNaN(val)) {
                        return includesResult === indexOfResult;
                    }
                    return true;
                }
            )
        );
    });
    
    test('Object.assign should merge objects', () => {
        fc.assert(
            fc.property(
                fc.object(),
                fc.object(),
                (obj1, obj2) => {
                    var result = Object.assign({}, obj1, obj2);
                    
                    // All keys from obj2 should be in result
                    return Object.keys(obj2).every(key => {
                        return result[key] === obj2[key];
                    });
                }
            )
        );
    });
    
    test('String.padStart should maintain length', () => {
        fc.assert(
            fc.property(
                fc.string(),
                fc.integer({ min: 0, max: 100 }),
                fc.string({ maxLength: 10 }),
                (str, targetLength, padString) => {
                    var padded = str.padStart(targetLength, padString || ' ');
                    
                    // Result should be at least targetLength
                    return padded.length >= Math.min(str.length, targetLength);
                }
            )
        );
    });
});
Note: Use Test262 suite for comprehensive ECMAScript spec compliance testing. Property-based testing finds edge cases automatically.

4. Performance Impact Measurement

Metric Native Baseline Acceptable Impact Measurement Tool
Execution Time 1x <2x native speed performance.now()
Memory Usage Baseline <20% increase performance.memory
Bundle Size 0 KB (native) <5 KB per polyfill webpack-bundle-analyzer
Parse Time 0 ms <10 ms total DevTools Performance
FCP Impact Baseline <100 ms delay Lighthouse

Example: Performance benchmarking

// performance-benchmark.js
function benchmark(name, fn, iterations) {
    // Warm up
    for (var i = 0; i < 100; i++) {
        fn();
    }
    
    // Measure
    var start = performance.now();
    for (var i = 0; i < iterations; i++) {
        fn();
    }
    var end = performance.now();
    
    var totalTime = end - start;
    var avgTime = totalTime / iterations;
    
    console.log(name + ':');
    console.log('  Total: ' + totalTime.toFixed(2) + 'ms');
    console.log('  Average: ' + (avgTime * 1000).toFixed(2) + 'μs');
    console.log('  Ops/sec: ' + Math.round(iterations / (totalTime / 1000)));
    
    return avgTime;
}

// Test Array.includes performance
var testArray = Array.from({ length: 1000 }, (_, i) => i);
var iterations = 10000;

var nativeTime = benchmark('Native Array.includes', function() {
    testArray.includes(500);
}, iterations);

// Test polyfill performance
delete Array.prototype.includes;
require('./array-includes.polyfill');

var polyfillTime = benchmark('Polyfill Array.includes', function() {
    testArray.includes(500);
}, iterations);

var slowdown = polyfillTime / nativeTime;
console.log('\nSlowdown factor: ' + slowdown.toFixed(2) + 'x');

if (slowdown > 2) {
    console.warn('WARNING: Polyfill is more than 2x slower than native!');
}

Example: Memory usage tracking

// memory-usage.test.js
describe('Memory usage tests', () => {
    // Chrome-specific memory measurement
    function measureMemory(fn) {
        if (!performance.memory) {
            console.warn('performance.memory not available');
            return null;
        }
        
        // Force garbage collection (requires --expose-gc flag)
        if (global.gc) {
            global.gc();
        }
        
        var before = performance.memory.usedJSHeapSize;
        fn();
        var after = performance.memory.usedJSHeapSize;
        
        return after - before;
    }
    
    test('Promise polyfill memory usage', () => {
        var memoryUsed = measureMemory(() => {
            var promises = [];
            for (var i = 0; i < 1000; i++) {
                promises.push(new Promise(resolve => resolve(i)));
            }
            return Promise.all(promises);
        });
        
        if (memoryUsed !== null) {
            var memoryMB = memoryUsed / (1024 * 1024);
            console.log('Memory used: ' + memoryMB.toFixed(2) + ' MB');
            
            // Should use less than 5 MB for 1000 promises
            expect(memoryMB).toBeLessThan(5);
        }
    });
    
    test('Map polyfill memory efficiency', () => {
        var memoryUsed = measureMemory(() => {
            var map = new Map();
            for (var i = 0; i < 10000; i++) {
                map.set('key' + i, { value: i });
            }
        });
        
        if (memoryUsed !== null) {
            var memoryKB = memoryUsed / 1024;
            console.log('Memory used: ' + memoryKB.toFixed(2) + ' KB');
            
            // Should be reasonably efficient
            expect(memoryKB).toBeLessThan(2000); // < 2 MB
        }
    });
});
Warning: Polyfills typically run 1.5-3x slower than native implementations. If performance is critical, consider dropping support for older browsers.

5. Automated Browser Testing with Polyfills

Tool Type Browsers CI Integration
Selenium WebDriver End-to-end All major browsers Excellent
Playwright End-to-end Chromium, Firefox, WebKit Excellent
Puppeteer End-to-end Chrome/Chromium only Good
TestCafe End-to-end All major browsers Good
Karma Test runner Multiple via launchers Excellent

Example: Karma configuration for multi-browser testing

// karma.conf.js
module.exports = function(config) {
    config.set({
        basePath: '',
        
        frameworks: ['jasmine'],
        
        files: [
            // Load polyfills first
            'polyfills/es5-shim.js',
            'polyfills/es6-shim.js',
            'polyfills/fetch.js',
            
            // Then test files
            'src/**/*.js',
            'test/**/*.spec.js'
        ],
        
        preprocessors: {
            'src/**/*.js': ['webpack', 'coverage'],
            'test/**/*.spec.js': ['webpack']
        },
        
        webpack: {
            mode: 'development',
            module: {
                rules: [
                    {
                        test: /\.js$/,
                        exclude: /node_modules/,
                        use: {
                            loader: 'babel-loader',
                            options: {
                                presets: ['@babel/preset-env']
                            }
                        }
                    }
                ]
            }
        },
        
        // Test against multiple browsers
        browsers: [
            'Chrome',
            'Firefox',
            'Safari',
            'IE11' // Requires IE11 launcher
        ],
        
        // Custom launchers for specific configurations
        customLaunchers: {
            ChromeHeadless_Custom: {
                base: 'ChromeHeadless',
                flags: [
                    '--no-sandbox',
                    '--disable-gpu',
                    '--disable-dev-shm-usage'
                ]
            },
            IE11: {
                base: 'IE',
                'x-ua-compatible': 'IE=EmulateIE11'
            }
        },
        
        reporters: ['progress', 'coverage'],
        
        coverageReporter: {
            type: 'html',
            dir: 'coverage/'
        },
        
        port: 9876,
        colors: true,
        logLevel: config.LOG_INFO,
        autoWatch: false,
        singleRun: true,
        concurrency: Infinity
    });
};

Example: GitHub Actions CI workflow

# .github/workflows/test.yml
name: Cross-browser Testing

on: [push, pull_request]

jobs:
    test:
        runs-on: ${{ matrix.os }}
        
        strategy:
            matrix:
                os: [ubuntu-latest, windows-latest, macos-latest]
                browser: [chrome, firefox, webkit]
                node-version: [14.x, 16.x, 18.x]
        
        steps:
            - uses: actions/checkout@v3
            
            - name: Setup Node.js
              uses: actions/setup-node@v3
              with:
                  node-version: ${{ matrix.node-version }}
            
            - name: Install dependencies
              run: npm ci
            
            - name: Install Playwright browsers
              run: npx playwright install --with-deps
            
            - name: Run tests
              run: npm test -- --project=${{ matrix.browser }}
            
            - name: Upload test results
              if: always()
              uses: actions/upload-artifact@v3
              with:
                  name: test-results-${{ matrix.os }}-${{ matrix.browser }}
                  path: test-results/
            
            - name: Upload coverage
              if: matrix.os == 'ubuntu-latest' && matrix.browser == 'chrome'
              uses: codecov/codecov-action@v3
              with:
                  files: ./coverage/lcov.info
Note: Automate testing across multiple browsers and OS combinations in CI/CD. Use headless browsers for speed, real browsers for final validation.

6. Mock and Stub Patterns for Testing

Pattern Use Case Implementation Restore Method
Mock Replace entire object/function jest.mock(), sinon.mock() Automatic in afterEach
Stub Replace specific method jest.fn(), sinon.stub() Restore original
Spy Track calls without replacing jest.spyOn(), sinon.spy() Restore or remove spy
Fake Working simplified implementation Custom implementation Manual restore
Dummy Placeholder, never used Empty object/function N/A

Example: Mocking browser APIs

// mock-apis.test.js
describe('API mocking for polyfill tests', () => {
    describe('fetch polyfill', () => {
        var originalFetch;
        
        beforeEach(() => {
            // Save and mock fetch
            originalFetch = global.fetch;
            
            global.fetch = jest.fn((url) => {
                return Promise.resolve({
                    ok: true,
                    status: 200,
                    json: () => Promise.resolve({ data: 'mocked' }),
                    text: () => Promise.resolve('mocked text')
                });
            });
        });
        
        afterEach(() => {
            // Restore original
            global.fetch = originalFetch;
        });
        
        test('should make request', async () => {
            var response = await fetch('/api/test');
            var data = await response.json();
            
            expect(fetch).toHaveBeenCalledWith('/api/test');
            expect(data).toEqual({ data: 'mocked' });
        });
    });
    
    describe('IntersectionObserver polyfill', () => {
        var mockObserve;
        var mockUnobserve;
        var mockDisconnect;
        
        beforeEach(() => {
            mockObserve = jest.fn();
            mockUnobserve = jest.fn();
            mockDisconnect = jest.fn();
            
            global.IntersectionObserver = jest.fn(function(callback) {
                this.observe = mockObserve;
                this.unobserve = mockUnobserve;
                this.disconnect = mockDisconnect;
                
                // Simulate intersection
                setTimeout(() => {
                    callback([
                        {
                            isIntersecting: true,
                            target: document.createElement('div'),
                            intersectionRatio: 1
                        }
                    ]);
                }, 0);
            });
        });
        
        test('should observe elements', (done) => {
            var element = document.createElement('div');
            
            var observer = new IntersectionObserver((entries) => {
                expect(entries[0].isIntersecting).toBe(true);
                expect(mockObserve).toHaveBeenCalledWith(element);
                done();
            });
            
            observer.observe(element);
        });
    });
    
    describe('localStorage mock', () => {
        var localStorageMock;
        
        beforeEach(() => {
            localStorageMock = (function() {
                var store = {};
                
                return {
                    getItem: function(key) {
                        return store[key] || null;
                    },
                    setItem: function(key, value) {
                        store[key] = String(value);
                    },
                    removeItem: function(key) {
                        delete store[key];
                    },
                    clear: function() {
                        store = {};
                    },
                    get length() {
                        return Object.keys(store).length;
                    },
                    key: function(index) {
                        var keys = Object.keys(store);
                        return keys[index] || null;
                    }
                };
            })();
            
            Object.defineProperty(global, 'localStorage', {
                value: localStorageMock,
                writable: true
            });
        });
        
        test('should store and retrieve items', () => {
            localStorage.setItem('key', 'value');
            expect(localStorage.getItem('key')).toBe('value');
            
            localStorage.removeItem('key');
            expect(localStorage.getItem('key')).toBeNull();
        });
        
        test('should track length', () => {
            expect(localStorage.length).toBe(0);
            
            localStorage.setItem('key1', 'value1');
            localStorage.setItem('key2', 'value2');
            
            expect(localStorage.length).toBe(2);
        });
    });
});

Example: Sinon stubs for complex scenarios

// sinon-stubs.test.js
var sinon = require('sinon');

describe('Sinon stub patterns', () => {
    describe('Date polyfill testing', () => {
        var clock;
        
        beforeEach(() => {
            // Fake timers
            clock = sinon.useFakeTimers(new Date('2025-01-01').getTime());
        });
        
        afterEach(() => {
            clock.restore();
        });
        
        test('should use fixed date', () => {
            var now = Date.now();
            expect(now).toBe(new Date('2025-01-01').getTime());
            
            // Advance time
            clock.tick(1000);
            expect(Date.now()).toBe(new Date('2025-01-01').getTime() + 1000);
        });
    });
    
    describe('Performance API stubbing', () => {
        var performanceStub;
        
        beforeEach(() => {
            performanceStub = sinon.stub(performance, 'now');
            performanceStub.returns(1000.5);
        });
        
        afterEach(() => {
            performanceStub.restore();
        });
        
        test('should return stubbed time', () => {
            expect(performance.now()).toBe(1000.5);
            
            // Change stub behavior
            performanceStub.returns(2000.5);
            expect(performance.now()).toBe(2000.5);
        });
    });
    
    describe('Network request stubbing', () => {
        var xhr, requests;
        
        beforeEach(() => {
            xhr = sinon.useFakeXMLHttpRequest();
            requests = [];
            
            xhr.onCreate = function(req) {
                requests.push(req);
            };
        });
        
        afterEach(() => {
            xhr.restore();
        });
        
        test('should capture XHR requests', () => {
            var callback = sinon.spy();
            
            var req = new XMLHttpRequest();
            req.open('GET', '/api/test');
            req.onload = callback;
            req.send();
            
            expect(requests.length).toBe(1);
            expect(requests[0].url).toBe('/api/test');
            
            // Respond to request
            requests[0].respond(200, 
                { 'Content-Type': 'application/json' },
                JSON.stringify({ success: true })
            );
            
            expect(callback.calledOnce).toBe(true);
        });
    });
});

Key Takeaways - Testing & Quality Assurance

  • Unit Tests: Remove native implementations to test polyfill code directly
  • Cross-browser: Test on real browsers (IE11, Safari) not just modern ones
  • Feature Parity: Compare with native behavior, use Test262 for spec compliance
  • Performance: Polyfills should be <2x slower, monitor bundle size impact
  • Automation: Integrate multi-browser testing in CI/CD pipeline
  • Mocking: Use stubs/mocks to test polyfills in isolation