Why Local Testing is a Accessibility Superpower
Before we dive into the technical bits, let's talk about why local testing is especially powerful for developers who rely on accessibility tools:
Screen Reader Friendly Testing
- Your screen reader can properly announce test results in your familiar IDE
- No struggling with web-based console outputs
- Jest's output formats work beautifully with screen readers
- You can navigate test files using your IDE's accessibility features
Consistent Workflow
- Test output appears in the same terminal you use for everything else
- Your keyboard shortcuts work exactly as expected
- Error messages are properly structured for accessibility tools
- Code navigation between tests and implementation is seamless
Debugging Without Drama
- Set breakpoints using your IDE's accessible debugging interface
- Step through code using familiar keyboard commands
- Inspect variables without relying on visual-only debugging tools
The Testing Architecture That Actually Makes Sense
ScriptRunner Connect's External Coding provides a testing setup that's both powerful and accessible. Here's what makes it special:
Runtime Mocks: Your Testing Best Friend
The node/runtimeMocks.ts file is like having a clone of ScriptRunner Connect's runtime environment living on your machine. It provides local implementations of all the cloud-specific functionality:
// Mocks for @sr-connect/convert package
global._convert = {
base64ToBuffer: (base64) => Buffer.from(base64, 'base64'),
base64ToText: (base64, encoding) => Buffer.from(base64, 'base64').toString(encoding ?? 'utf8'),
// ... more conversion utilities
};
Think of it as a stand-in actor who knows all the lines perfectly – your tests run locally but behave exactly like they would in the cloud.
Pre-configured Jest: Testing Made Simple
No more wrestling with Jest configurations or TypeScript setup. Everything just works:
- TypeScript compilation handled automatically
- ES modules properly configured
- Screen reader friendly output formatting
- Parallel test execution (because waiting is boring)
Setting Up Your Test Files: Where Everything Goes
Before we start writing tests, let's understand exactly where to create our test files and what the project structure should look like. This is crucial for screen reader users who need to navigate the file structure efficiently.
Project Structure for Testing
Here's how your project should be organized after following Series #1: Your IDE, Your Rules - Setting Up Accessible Development and adding the test files we'll create in this post:
your-scriptrunner-connect-project/ ├── node/ # Local development environment │ ├── runtimeMocks.ts # Runtime environment mocks │ ├── client/jira/cloud/ # Mock API clients for testing (create this directory) │ │ └── JiraCloudClientMock.ts # Mock Jira client (we'll create this) │ ├── tests/ # All test files go here │ │ ├── cloud/atlassian/jira/ # Mirror your scripts structure (create this directory) │ │ │ └── Projects.test.ts # Test for Projects.ts (we'll create this) │ │ └── OnJiraCloudProjectCreated.test.ts # Test for main event handler (we'll create this) │ ├── apiRegistry.ts # API client registry (we'll update this) │ ├── jest.config.ts # Jest configuration │ └── tsconfig.json # Node-specific TypeScript config ├── scripts/ # Your ScriptRunner scripts │ ├── OnJiraCloudProjectCreated.ts # Main event handler │ ├── cloud/atlassian/jira/ # API integration helpers │ │ └── Projects.ts # Projects API integration │ └── api/ # API connections ├── package.json # Dependencies └── tsconfig.json # Main TypeScript configuration
Files We'll Create in This Post
We'll be creating these files to demonstrate the testing patterns:
node/client/jira/cloud/JiraCloudClientMock.ts- Mock Jira client for testing without real API callsnode/tests/cloud/atlassian/jira/Projects.test.ts- Tests for the Projects API integrationnode/tests/OnJiraCloudProjectCreated.test.ts- Tests for the main event handler
We'll also update: 4. node/apiRegistry.ts - Update to use the mock client for testing
Accessibility Note: This mirrored structure makes it easy for screen readers to navigate between implementation and test files. If you're testing scripts/cloud/atlassian/jira/Projects.ts, its test lives at node/tests/cloud/atlassian/jira/Projects.test.ts – logical and predictable!
Creating Your First Test: A Step-by-Step Journey
Now let's create the mock client and tests for the Projects API integration we built in Series #1: Your IDE, Your Rules - Setting Up Accessible Development. This example will show you the patterns that work beautifully with accessibility tools.
Step 1: Create the Mock Jira Client
First, we need a mock API client that doesn't make real HTTP requests but returns predictable test data. Create the file node/client/jira/cloud/JiraCloudClientMock.ts:
// node/client/jira/cloud/JiraCloudClientMock.tsimport { PlatformImplementation, Request, Response as CommonsResponse, Headers as CommonsHeaders, BaseApiCore,} from '@managed-api/commons-core';import { JiraCloudApiCore } from '@managed-api/jira-cloud-v3-core';/** * Mock implementation of Jira Cloud API client for testing. * * This client returns predictable test data without making real HTTP requests, * perfect for testing your ScriptRunner Connect scripts locally. */export class JiraCloudClientMock extends JiraCloudApiCore { constructor() { super(); console.log('Mock Jira client initialized for testing'); } protected getPlatformImplementation(): PlatformImplementation { return { buffer: { encode: (input) => Buffer.from(input, 'utf-8'), decode: (input) => Buffer.from(input).toString('utf-8'), }, performHttpCall: (request) => this.performHttpCall(request), }; } private async performHttpCall(request: Request): Promise<CommonsResponse> { // Log mock API calls for debugging (screen reader friendly) console.log(`Mock API Call: ${request.method} ${request.url}`); switch (request.url) { case '/rest/api/3/project/search': return super.buildResponse( request.url, 200, 'OK', new CommonsHeaders(), JSON.stringify({ isLast: true, maxResults: 50, startAt: 0, total: 1, values: [ { "avatarUrls": { }, "id": "10000", "insight": { "lastIssueUpdateTime": "2021-04-22T05:37:05.000+0000", "totalIssueCount": 100 }, "key": "TEST", "name": "Test Project", "projectCategory": { "description": "Test Project Category", "id": "10000", "name": "TESTING", }, "simplified": false, "style": "classic" } ], }), ); default: throw new Error(`No mock implementation found for: ${request.url}`); } }}Step 2: Update the API Registry for Testing
Now we need to update node/apiRegistry.ts to use our mock client when running tests. This allows your tests to use mock data while your real scripts can still use actual API calls:
import { config } from 'dotenv';import { BaseApiCore } from '@managed-api/commons-core';import { JiraCloudClientMock } from './client/jira/cloud/JiraCloudClientMock';// Load environment variables from .env fileconfig();export type ApiRegistry = Record<string, BaseApiCore>;export const API_REGISTRY: ApiRegistry = { // Declare here locally implemented API connections // Client mocks HTTP requests 'jira/cloud': new JiraCloudClientMock()};Step 3: Create the Projects API Test
Create the file node/tests/cloud/atlassian/jira/Projects.test.ts:
import '../../../../runtimeMocks';import { jest } from '@jest/globals';import { getProjects } from "../../../../../scripts/cloud/atlassian/jira/Projects";// Array for intercepted console.log messageslet interceptedConsoleLogs: string[] = [];// Function for getting mocked Context objectexport function getContext(ev: EV): Context<EV> { return { availableMemory: 400, invocationId: 'INVOCATION_ID', triggerType: 'MANUAL', rootTriggerType: 'MANUAL', startTime: Date.now(), timeout: 900000, environment: { uid: 'ENVIRONMENT_UID', name: 'Default', vars: ev, }, };}beforeAll(() => { // Mock all console.log methods to intercept messages and store them jest.spyOn(console, 'log').mockImplementation((...args) => interceptedConsoleLogs.push(args.join(' ')));});describe('Test projects', () => { test('Test successful list of projects', async () => { const projects = await getProjects(); expect(projects.length).toBeGreaterThan(0) });});afterAll(() => { jest.clearAllMocks();});
Testing Event Handlers: Where the Magic Happens
Now let's create a test for your main event handler. Create the file node/tests/OnJiraCloudProjectCreated.test.ts:
import '../runtimeMocks';import { jest } from '@jest/globals';import { ProjectCreatedEvent } from '@sr-connect/jira-cloud/events';import OnJiraCloudProjectCreated from '../../scripts/OnJiraCloudProjectCreated'import { getContext } from './cloud/atlassian/jira/Projects.test';describe('OnJiraCloudProjectCreated', () => { let consoleLogs: string[] = []; beforeEach(() => { consoleLogs = []; jest.spyOn(console, 'log').mockImplementation((...args) => consoleLogs.push(args.join(' ')) ); }); afterEach(() => { jest.clearAllMocks(); }); test('Should handle project created event', async () => { // Mock event data const mockEvent: ProjectCreatedEvent = { webhookEvent: "project_created", project: { id: 10000, key: 'TEST', name: 'Test Project', self: '' }, timestamp: Date.now() }; // Mock context const context = getContext({}); // Execute the event handler await OnJiraCloudProjectCreated(mockEvent, context); // Verify the script executed (console.log outputs the string plus event and context) expect(consoleLogs.length).toBeGreaterThan(0); expect(consoleLogs[0]).toContain('Project Created event triggered'); });});
Running Your Tests: The Accessibility-First Workflow
Now that you've created your test files, let's run them with your accessible development workflow:
- Run All Tests with Audio Feedback
npm test
- Focus on Specific Tests
npm test -- Projects.test.ts
This runs only the tests you're working on, reducing cognitive load and keeping output manageable.
- Watch Mode for Continuous Feedback
npm test -- --watch
Jest will re-run tests automatically when you save files. Your screen reader will announce results immediately, creating a tight feedback loop.
- Detailed Output When Needed
npm test -- --verbose
Gets detailed output for debugging. Screen readers handle this verbose output well, unlike many web-based testing interfaces.
What We've Accomplished
Testing victory achieved! You've just transformed from someone who deploys and prays into someone who tests with confidence. Your screen reader can navigate test results like a boss, Jest is your new best friend, your mock clients are serving up predictable test data, and those runtime mocks are working harder than a coffee machine on Monday morning.
Let's be honest: you've basically become the testing wizard your team didn't know they needed. While others are still debugging in production (we don't judge... much), you're catching bugs locally with the elegance of a screen reader announcing "15 tests passed" in your favourite IDE. Plus, your mock clients ensure you never accidentally spam real Jira instances during testing.
What's brewing next:
- In ScriptRunner Connect External Coding Series #3: API Magic - Building Local Clients That Don't Make You Want to Throw Your Computer Out the Window, we're going to let you play with real Atlassian data without breaking anything important – think of it as giving you the keys to the API kingdom but with training wheels.
- Then ScriptRunner Connect External Coding Series #4: AI-Powered Development - Your New Coding Superpower (That Actually Understands Accessibility) brings in AI that's so smart, it probably tests its own code before suggesting it to you.
About the Author and Development Team
Rafael Pinto Sperafico, Senior Atlassian Consultant and technical architect behind the ScriptRunner Connect External Coding Series, brings years of hands-on experience helping organisations transform their Atlassian automation development workflows. As part of Ambientia's expert Atlassian team, Rafael Pinto Sperafico specialises in accessibility-first development practices, enterprise-grade ScriptRunner Connect implementations, and cutting-edge AI-assisted development methodologies. This series emerged from real-world client work where teams needed professional, accessible development environments that didn't force them to abandon their familiar tools or compromise on functionality.
The comprehensive insights shared throughout this series reflect the collective expertise of Ambientia's Atlassian Consultants Team – experienced professionals passionate about making powerful development tools accessible to all developers. Our team combines technical expertise in ScriptRunner Connect with a deep understanding of accessibility requirements, helping development teams explore professional local development environments that integrate with cloud execution. We specialise in the methodologies covered in this series: WebStorm SFTP configuration, comprehensive testing frameworks, local API development, and AI-powered automation workflows that respect accessibility standards.
Ready to transform your ScriptRunner Connect development experience? Whether you're struggling with browser-based development limitations, need to implement accessibility-compliant automation workflows, or want to leverage AI assistance like Claude Code while maintaining professional development standards, Ambientia's Atlassian experts are here to help. We offer tailored consultancy for External Coding implementations, accessibility audits for existing automations, and comprehensive training on the workflows covered in this series. Contact Ambientia today to discuss how our proven External Coding methodologies can accelerate your team's productivity while ensuring every developer – regardless of their assistive technology needs – can contribute effectively to your Atlassian automation projects.
Subscribe to our content
Do you want our content in your email? Among other things, we offer information on service management and customer experience development
More related
Atlassian
ScriptRunner Connect External Coding Series #1: Your IDE, Your Rules
Set up ScriptRunner Connect External Coding with your local IDE for accessible, professional Atlassian automation development.
Read more
Published 25.9.2025 | Updated 26.9.2025
Atlassian
Simplify Your Atlassian Integrations: How Generic Connector Reduces Costs and Complexity
Simplify Atlassian integrations with ScriptRunner's Generic Connector, reducing costs and complexity by consolidating multiple connectors into one per instance.
Read more
Published 3.11.2025 | Updated 03.11.2025
