diff --git a/src/application/Common/CustomError.ts b/src/application/Common/CustomError.ts
index 8a31884df..eff52bf6e 100644
--- a/src/application/Common/CustomError.ts
+++ b/src/application/Common/CustomError.ts
@@ -1,4 +1,4 @@
-import { isFunction } from '@/TypeHelpers';
+import { isFunction, type ConstructorArguments } from '@/TypeHelpers';
 
 /*
   Provides a unified and resilient way to extend errors across platforms.
@@ -12,8 +12,8 @@ import { isFunction } from '@/TypeHelpers';
     > https://web.archive.org/web/20230810014143/https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
 */
 export abstract class CustomError extends Error {
-  constructor(message?: string, options?: ErrorOptions) {
-    super(message, options);
+  constructor(...args: ConstructorArguments<typeof Error>) {
+    super(...args);
 
     fixPrototype(this, new.target.prototype);
     ensureStackTrace(this);
diff --git a/src/application/Parser/CategoryParser.ts b/src/application/Parser/CategoryParser.ts
index ccaff8e8d..1eaa3fbc8 100644
--- a/src/application/Parser/CategoryParser.ts
+++ b/src/application/Parser/CategoryParser.ts
@@ -3,10 +3,12 @@ import type {
 } from '@/application/collections/';
 import { Script } from '@/domain/Script';
 import { Category } from '@/domain/Category';
-import { NodeValidator } from '@/application/Parser/NodeValidation/NodeValidator';
-import { NodeType } from '@/application/Parser/NodeValidation/NodeType';
-import { parseDocs } from './DocumentationParser';
-import { parseScript } from './Script/ScriptParser';
+import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
+import type { ICategory } from '@/domain/ICategory';
+import { parseDocs, type DocsParser } from './DocumentationParser';
+import { parseScript, type ScriptParser } from './Script/ScriptParser';
+import { createNodeDataValidator, type NodeDataValidator, type NodeDataValidatorFactory } from './NodeValidation/NodeDataValidator';
+import { NodeDataType } from './NodeValidation/NodeDataType';
 import type { ICategoryCollectionParseContext } from './Script/ICategoryCollectionParseContext';
 
 let categoryIdCounter = 0;
@@ -14,96 +16,108 @@ let categoryIdCounter = 0;
 export function parseCategory(
   category: CategoryData,
   context: ICategoryCollectionParseContext,
-  factory: CategoryFactoryType = CategoryFactory,
+  utilities: CategoryParserUtilities = DefaultCategoryParserUtilities,
 ): Category {
   return parseCategoryRecursively({
     categoryData: category,
     context,
-    factory,
+    utilities,
   });
 }
 
-interface ICategoryParseContext {
-  readonly categoryData: CategoryData,
-  readonly context: ICategoryCollectionParseContext,
-  readonly factory: CategoryFactoryType,
-  readonly parentCategory?: CategoryData,
+interface CategoryParseContext {
+  readonly categoryData: CategoryData;
+  readonly context: ICategoryCollectionParseContext;
+  readonly parentCategory?: CategoryData;
+  readonly utilities: CategoryParserUtilities;
 }
 
-function parseCategoryRecursively(context: ICategoryParseContext): Category | never {
-  ensureValidCategory(context.categoryData, context.parentCategory);
-  const children: ICategoryChildren = {
-    subCategories: new Array<Category>(),
-    subScripts: new Array<Script>(),
+function parseCategoryRecursively(
+  context: CategoryParseContext,
+): Category | never {
+  const validator = ensureValidCategory(context);
+  const children: CategoryChildren = {
+    subcategories: new Array<Category>(),
+    subscripts: new Array<Script>(),
   };
   for (const data of context.categoryData.children) {
     parseNode({
       nodeData: data,
       children,
       parent: context.categoryData,
-      factory: context.factory,
+      utilities: context.utilities,
       context: context.context,
     });
   }
   try {
-    return context.factory(
-      /* id: */ categoryIdCounter++,
-      /* name: */ context.categoryData.category,
-      /* docs: */ parseDocs(context.categoryData),
-      /* categories: */ children.subCategories,
-      /* scripts: */ children.subScripts,
+    return context.utilities.createCategory({
+      id: categoryIdCounter++,
+      name: context.categoryData.category,
+      docs: context.utilities.parseDocs(context.categoryData),
+      subcategories: children.subcategories,
+      scripts: children.subscripts,
+    });
+  } catch (error) {
+    throw context.utilities.wrapError(
+      error,
+      validator.createContextualErrorMessage('Failed to parse category.'),
     );
-  } catch (err) {
-    return new NodeValidator({
-      type: NodeType.Category,
-      selfNode: context.categoryData,
-      parentNode: context.parentCategory,
-    }).throw(err.message);
   }
 }
 
-function ensureValidCategory(category: CategoryData, parentCategory?: CategoryData) {
-  new NodeValidator({
-    type: NodeType.Category,
-    selfNode: category,
-    parentNode: parentCategory,
-  })
-    .assertDefined(category)
-    .assertValidName(category.category)
-    .assert(
-      () => category.children.length > 0,
-      `"${category.category}" has no children.`,
-    );
+function ensureValidCategory(
+  context: CategoryParseContext,
+): NodeDataValidator {
+  const category = context.categoryData;
+  const validator: NodeDataValidator = context.utilities.createValidator({
+    type: NodeDataType.Category,
+    selfNode: context.categoryData,
+    parentNode: context.parentCategory,
+  });
+  validator.assertDefined(category);
+  validator.assertValidName(category.category);
+  validator.assert(
+    () => Boolean(category.children) && category.children.length > 0,
+    `"${category.category}" has no children.`,
+  );
+  return validator;
 }
 
-interface ICategoryChildren {
-  subCategories: Category[];
-  subScripts: Script[];
+interface CategoryChildren {
+  readonly subcategories: Category[];
+  readonly subscripts: Script[];
 }
 
-interface INodeParseContext {
+interface NodeParseContext {
   readonly nodeData: CategoryOrScriptData;
-  readonly children: ICategoryChildren;
+  readonly children: CategoryChildren;
   readonly parent: CategoryData;
-  readonly factory: CategoryFactoryType;
   readonly context: ICategoryCollectionParseContext;
+
+  readonly utilities: CategoryParserUtilities;
 }
-function parseNode(context: INodeParseContext) {
-  const validator = new NodeValidator({ selfNode: context.nodeData, parentNode: context.parent });
+
+function parseNode(context: NodeParseContext) {
+  const validator: NodeDataValidator = context.utilities.createValidator({
+    selfNode: context.nodeData,
+    parentNode: context.parent,
+  });
   validator.assertDefined(context.nodeData);
+  validator.assert(
+    () => isCategory(context.nodeData) || isScript(context.nodeData),
+    'Node is neither a category or a script.',
+  );
   if (isCategory(context.nodeData)) {
     const subCategory = parseCategoryRecursively({
       categoryData: context.nodeData,
       context: context.context,
-      factory: context.factory,
       parentCategory: context.parent,
+      utilities: context.utilities,
     });
-    context.children.subCategories.push(subCategory);
-  } else if (isScript(context.nodeData)) {
-    const script = parseScript(context.nodeData, context.context);
-    context.children.subScripts.push(script);
-  } else {
-    validator.throw('Node is neither a category or a script.');
+    context.children.subcategories.push(subCategory);
+  } else { // A script
+    const script = context.utilities.parseScript(context.nodeData, context.context);
+    context.children.subscripts.push(script);
   }
 }
 
@@ -123,11 +137,35 @@ function hasCall(data: unknown) {
   return hasProperty(data, 'call');
 }
 
-function hasProperty(object: unknown, propertyName: string) {
+function hasProperty(
+  object: unknown,
+  propertyName: string,
+): object is NonNullable<object> {
+  if (typeof object !== 'object') {
+    return false;
+  }
+  if (object === null) { // `typeof object` is `null`
+    return false;
+  }
   return Object.prototype.hasOwnProperty.call(object, propertyName);
 }
 
-export type CategoryFactoryType = (
-  ...parameters: ConstructorParameters<typeof Category>) => Category;
+export type CategoryFactory = (
+  ...parameters: ConstructorParameters<typeof Category>
+) => ICategory;
+
+interface CategoryParserUtilities {
+  readonly createCategory: CategoryFactory;
+  readonly wrapError: ErrorWithContextWrapper;
+  readonly createValidator: NodeDataValidatorFactory;
+  readonly parseScript: ScriptParser;
+  readonly parseDocs: DocsParser;
+}
 
-const CategoryFactory: CategoryFactoryType = (...parameters) => new Category(...parameters);
+const DefaultCategoryParserUtilities: CategoryParserUtilities = {
+  createCategory: (...parameters) => new Category(...parameters),
+  wrapError: wrapErrorWithAdditionalContext,
+  createValidator: createNodeDataValidator,
+  parseScript,
+  parseDocs,
+};
diff --git a/src/application/Parser/ContextualError.ts b/src/application/Parser/ContextualError.ts
new file mode 100644
index 000000000..5c945620b
--- /dev/null
+++ b/src/application/Parser/ContextualError.ts
@@ -0,0 +1,42 @@
+import { CustomError } from '@/application/Common/CustomError';
+
+export interface ErrorWithContextWrapper {
+  (
+    error: Error,
+    additionalContext: string,
+  ): Error;
+}
+
+export const wrapErrorWithAdditionalContext: ErrorWithContextWrapper = (
+  error: Error,
+  additionalContext: string,
+) => {
+  return (error instanceof ContextualError ? error : new ContextualError(error))
+    .withAdditionalContext(additionalContext);
+};
+
+/* AggregateError is similar but isn't well-serialized or displayed (via console.log) by browsers */
+class ContextualError extends CustomError {
+  private readonly additionalContext = new Array<string>();
+
+  constructor(
+    public readonly innerError: Error,
+  ) {
+    super();
+  }
+
+  public withAdditionalContext(additionalContext: string): this {
+    this.additionalContext.push(additionalContext);
+    return this;
+  }
+
+  public get message(): string { // toString() is not used when Chromium logs it on console
+    return [
+      '\n',
+      this.innerError.message,
+      '\n',
+      'Additional context:',
+      ...this.additionalContext.map((context, index) => `${index + 1}: ${context}`),
+    ].join('\n');
+  }
+}
diff --git a/src/application/Parser/DocumentationParser.ts b/src/application/Parser/DocumentationParser.ts
index 2a177fb20..9ac70fcd9 100644
--- a/src/application/Parser/DocumentationParser.ts
+++ b/src/application/Parser/DocumentationParser.ts
@@ -1,7 +1,7 @@
 import type { DocumentableData, DocumentationData } from '@/application/collections/';
 import { isString, isArray } from '@/TypeHelpers';
 
-export function parseDocs(documentable: DocumentableData): readonly string[] {
+export const parseDocs: DocsParser = (documentable) => {
   const { docs } = documentable;
   if (!docs) {
     return [];
@@ -9,6 +9,12 @@ export function parseDocs(documentable: DocumentableData): readonly string[] {
   let result = new DocumentationContainer();
   result = addDocs(docs, result);
   return result.getAll();
+};
+
+export interface DocsParser {
+  (
+    documentable: DocumentableData,
+  ): readonly string[];
 }
 
 function addDocs(
diff --git a/src/application/Parser/NodeValidation/NodeDataError.ts b/src/application/Parser/NodeValidation/NodeDataError.ts
deleted file mode 100644
index 970498f94..000000000
--- a/src/application/Parser/NodeValidation/NodeDataError.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { CustomError } from '@/application/Common/CustomError';
-import { NodeType } from './NodeType';
-import type { NodeData } from './NodeData';
-
-export class NodeDataError extends CustomError {
-  constructor(message: string, public readonly context: INodeDataErrorContext) {
-    super(createMessage(message, context));
-  }
-}
-
-export interface INodeDataErrorContext {
-  readonly type?: NodeType;
-  readonly selfNode: NodeData;
-  readonly parentNode?: NodeData;
-}
-
-function createMessage(errorMessage: string, context: INodeDataErrorContext) {
-  let message = '';
-  if (context.type !== undefined) {
-    message += `${NodeType[context.type]}: `;
-  }
-  message += errorMessage;
-  message += `\n${dump(context)}`;
-  return message;
-}
-
-function dump(context: INodeDataErrorContext): string {
-  const printJson = (obj: unknown) => JSON.stringify(obj, undefined, 2);
-  let output = `Self: ${printJson(context.selfNode)}`;
-  if (context.parentNode) {
-    output += `\nParent: ${printJson(context.parentNode)}`;
-  }
-  return output;
-}
diff --git a/src/application/Parser/NodeValidation/NodeDataErrorContext.ts b/src/application/Parser/NodeValidation/NodeDataErrorContext.ts
new file mode 100644
index 000000000..f5e6fe852
--- /dev/null
+++ b/src/application/Parser/NodeValidation/NodeDataErrorContext.ts
@@ -0,0 +1,25 @@
+import type { CategoryData, ScriptData } from '@/application/collections/';
+import { NodeDataType } from './NodeDataType';
+import type { NodeData } from './NodeData';
+
+export type NodeDataErrorContext = {
+  readonly parentNode?: CategoryData;
+} & (CategoryNodeErrorContext | ScriptNodeErrorContext | UnknownNodeErrorContext);
+
+export type CategoryNodeErrorContext = {
+  readonly type: NodeDataType.Category;
+  readonly selfNode: CategoryData;
+  readonly parentNode?: CategoryData;
+};
+
+export type ScriptNodeErrorContext = {
+  readonly type: NodeDataType.Script;
+  readonly selfNode: ScriptData;
+  readonly parentNode?: CategoryData;
+};
+
+export type UnknownNodeErrorContext = {
+  readonly type?: undefined;
+  readonly selfNode: NodeData;
+  readonly parentNode?: CategoryData;
+};
diff --git a/src/application/Parser/NodeValidation/NodeDataErrorContextMessage.ts b/src/application/Parser/NodeValidation/NodeDataErrorContextMessage.ts
new file mode 100644
index 000000000..70956697e
--- /dev/null
+++ b/src/application/Parser/NodeValidation/NodeDataErrorContextMessage.ts
@@ -0,0 +1,35 @@
+import { NodeDataType } from './NodeDataType';
+import type { NodeDataErrorContext } from './NodeDataErrorContext';
+import type { NodeData } from './NodeData';
+
+export interface NodeContextErrorMessageCreator {
+  (
+    errorMessage: string,
+    context: NodeDataErrorContext,
+  ): string;
+}
+
+export const createNodeContextErrorMessage: NodeContextErrorMessageCreator = (
+  errorMessage,
+  context,
+) => {
+  let message = '';
+  if (context.type !== undefined) {
+    message += `${NodeDataType[context.type]}: `;
+  }
+  message += errorMessage;
+  message += `\n${getErrorContextDetails(context)}`;
+  return message;
+};
+
+function getErrorContextDetails(context: NodeDataErrorContext): string {
+  let output = `Self: ${printNodeDataAsJson(context.selfNode)}`;
+  if (context.parentNode) {
+    output += `\nParent: ${printNodeDataAsJson(context.parentNode)}`;
+  }
+  return output;
+}
+
+function printNodeDataAsJson(node: NodeData): string {
+  return JSON.stringify(node, undefined, 2);
+}
diff --git a/src/application/Parser/NodeValidation/NodeDataType.ts b/src/application/Parser/NodeValidation/NodeDataType.ts
new file mode 100644
index 000000000..cc4dac96e
--- /dev/null
+++ b/src/application/Parser/NodeValidation/NodeDataType.ts
@@ -0,0 +1,4 @@
+export enum NodeDataType {
+  Script,
+  Category,
+}
diff --git a/src/application/Parser/NodeValidation/NodeDataValidator.ts b/src/application/Parser/NodeValidation/NodeDataValidator.ts
new file mode 100644
index 000000000..757a3e217
--- /dev/null
+++ b/src/application/Parser/NodeValidation/NodeDataValidator.ts
@@ -0,0 +1,69 @@
+import { isString } from '@/TypeHelpers';
+import { type NodeDataErrorContext } from './NodeDataErrorContext';
+import { createNodeContextErrorMessage, type NodeContextErrorMessageCreator } from './NodeDataErrorContextMessage';
+import type { NodeData } from './NodeData';
+
+export interface NodeDataValidatorFactory {
+  (context: NodeDataErrorContext): NodeDataValidator;
+}
+
+export interface NodeDataValidator {
+  assertValidName(nameValue: string): void;
+  assertDefined(
+    node: NodeData | undefined,
+  ): asserts node is NonNullable<NodeData> & void;
+  assert(
+    validationPredicate: () => boolean,
+    errorMessage: string,
+  ): asserts validationPredicate is (() => true);
+  createContextualErrorMessage(errorMessage: string): string;
+}
+
+export const createNodeDataValidator
+: NodeDataValidatorFactory = (context) => new ContextualNodeDataValidator(context);
+
+export class ContextualNodeDataValidator implements NodeDataValidator {
+  constructor(
+    private readonly context: NodeDataErrorContext,
+    private readonly createErrorMessage
+    : NodeContextErrorMessageCreator = createNodeContextErrorMessage,
+  ) {
+
+  }
+
+  public assertValidName(nameValue: string): void {
+    this.assert(() => Boolean(nameValue), 'missing name');
+    this.assert(
+      () => isString(nameValue),
+      `Name (${JSON.stringify(nameValue)}) is not a string but ${typeof nameValue}.`,
+    );
+  }
+
+  public assertDefined(
+    node: NodeData,
+  ): asserts node is NonNullable<NodeData> {
+    this.assert(
+      () => node !== undefined && node !== null && Object.keys(node).length > 0,
+      'missing node data',
+    );
+  }
+
+  public assert(
+    validationPredicate: () => boolean,
+    errorMessage: string,
+  ): asserts validationPredicate is (() => true) {
+    if (!validationPredicate()) {
+      this.throw(errorMessage);
+    }
+  }
+
+  public createContextualErrorMessage(errorMessage: string): string {
+    return this.createErrorMessage(errorMessage, this.context);
+  }
+
+  private throw(errorMessage: string): never {
+    throw new Error(
+      this.createContextualErrorMessage(errorMessage),
+    );
+  }
+}
diff --git a/src/application/Parser/NodeValidation/NodeType.ts b/src/application/Parser/NodeValidation/NodeType.ts
deleted file mode 100644
index 648db72d2..000000000
--- a/src/application/Parser/NodeValidation/NodeType.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export enum NodeType {
-  Script,
-  Category,
-}
diff --git a/src/application/Parser/NodeValidation/NodeValidator.ts b/src/application/Parser/NodeValidation/NodeValidator.ts
deleted file mode 100644
index 37860210b..000000000
--- a/src/application/Parser/NodeValidation/NodeValidator.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { isString } from '@/TypeHelpers';
-import { type INodeDataErrorContext, NodeDataError } from './NodeDataError';
-import type { NodeData } from './NodeData';
-
-export class NodeValidator {
-  constructor(private readonly context: INodeDataErrorContext) {
-
-  }
-
-  public assertValidName(nameValue: string) {
-    return this
-      .assert(
-        () => Boolean(nameValue),
-        'missing name',
-      )
-      .assert(
-        () => isString(nameValue),
-        `Name (${JSON.stringify(nameValue)}) is not a string but ${typeof nameValue}.`,
-      );
-  }
-
-  public assertDefined(node: NodeData) {
-    return this.assert(
-      () => node !== undefined && node !== null && Object.keys(node).length > 0,
-      'missing node data',
-    );
-  }
-
-  public assert(validationPredicate: () => boolean, errorMessage: string) {
-    if (!validationPredicate()) {
-      this.throw(errorMessage);
-    }
-    return this;
-  }
-
-  public throw(errorMessage: string): never {
-    throw new NodeDataError(errorMessage, this.context);
-  }
-}
diff --git a/src/application/Parser/Script/Compiler/Expressions/Expression/Expression.ts b/src/application/Parser/Script/Compiler/Expressions/Expression/Expression.ts
index 6f89b2603..8614ca64b 100644
--- a/src/application/Parser/Script/Compiler/Expressions/Expression/Expression.ts
+++ b/src/application/Parser/Script/Compiler/Expressions/Expression/Expression.ts
@@ -7,15 +7,18 @@ import type { IReadOnlyFunctionParameterCollection } from '../../Function/Parame
 import type { IExpression } from './IExpression';
 
 export type ExpressionEvaluator = (context: IExpressionEvaluationContext) => string;
+
 export class Expression implements IExpression {
   public readonly parameters: IReadOnlyFunctionParameterCollection;
 
-  constructor(
-    public readonly position: ExpressionPosition,
-    public readonly evaluator: ExpressionEvaluator,
-    parameters?: IReadOnlyFunctionParameterCollection,
-  ) {
-    this.parameters = parameters ?? new FunctionParameterCollection();
+  public readonly position: ExpressionPosition;
+
+  public readonly evaluator: ExpressionEvaluator;
+
+  constructor(parameters: ExpressionInitParameters) {
+    this.parameters = parameters.parameters ?? new FunctionParameterCollection();
+    this.evaluator = parameters.evaluator;
+    this.position = parameters.position;
   }
 
   public evaluate(context: IExpressionEvaluationContext): string {
@@ -26,6 +29,12 @@ export class Expression implements IExpression {
   }
 }
 
+export interface ExpressionInitParameters {
+  readonly position: ExpressionPosition,
+  readonly evaluator: ExpressionEvaluator,
+  readonly parameters?: IReadOnlyFunctionParameterCollection,
+}
+
 function validateThatAllRequiredParametersAreSatisfied(
   parameters: IReadOnlyFunctionParameterCollection,
   args: IReadOnlyFunctionCallArgumentCollection,
diff --git a/src/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPositionFactory.ts b/src/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPositionFactory.ts
index 10eb73ae6..6eac6b3da 100644
--- a/src/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPositionFactory.ts
+++ b/src/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPositionFactory.ts
@@ -1,8 +1,13 @@
 import { ExpressionPosition } from './ExpressionPosition';
 
-export function createPositionFromRegexFullMatch(
-  match: RegExpMatchArray,
-): ExpressionPosition {
+export interface ExpressionPositionFactory {
+  (
+    match: RegExpMatchArray,
+  ): ExpressionPosition
+}
+
+export const createPositionFromRegexFullMatch
+: ExpressionPositionFactory = (match) => {
   const startPos = match.index;
   if (startPos === undefined) {
     throw new Error(`Regex match did not yield any results: ${JSON.stringify(match)}`);
@@ -13,4 +18,4 @@ export function createPositionFromRegexFullMatch(
   }
   const endPos = startPos + fullMatch.length;
   return new ExpressionPosition(startPos, endPos);
-}
+};
diff --git a/src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts b/src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts
index 16b8663dc..1949df037 100644
--- a/src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts
+++ b/src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts
@@ -3,10 +3,10 @@ import { WithParser } from '../SyntaxParsers/WithParser';
 import type { IExpression } from '../Expression/IExpression';
 import type { IExpressionParser } from './IExpressionParser';
 
-const Parsers = [
+const Parsers: readonly IExpressionParser[] = [
   new ParameterSubstitutionParser(),
   new WithParser(),
-];
+] as const;
 
 export class CompositeExpressionParser implements IExpressionParser {
   public constructor(private readonly leafs: readonly IExpressionParser[] = Parsers) {
diff --git a/src/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.ts b/src/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.ts
index 8a88c860d..6e8d0e7da 100644
--- a/src/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.ts
+++ b/src/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.ts
@@ -1,53 +1,127 @@
+import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
 import { Expression, type ExpressionEvaluator } from '../../Expression/Expression';
-import { FunctionParameterCollection } from '../../../Function/Parameter/FunctionParameterCollection';
-import { createPositionFromRegexFullMatch } from '../../Expression/ExpressionPositionFactory';
+import { createPositionFromRegexFullMatch, type ExpressionPositionFactory } from '../../Expression/ExpressionPositionFactory';
+import { createFunctionParameterCollection, type FunctionParameterCollectionFactory } from '../../../Function/Parameter/FunctionParameterCollectionFactory';
 import type { IExpressionParser } from '../IExpressionParser';
 import type { IExpression } from '../../Expression/IExpression';
 import type { IFunctionParameter } from '../../../Function/Parameter/IFunctionParameter';
+import type { IFunctionParameterCollection, IReadOnlyFunctionParameterCollection } from '../../../Function/Parameter/IFunctionParameterCollection';
+
+export interface RegexParserUtilities {
+  readonly wrapError: ErrorWithContextWrapper;
+  readonly createPosition: ExpressionPositionFactory;
+  readonly createExpression: ExpressionFactory;
+  readonly createParameterCollection: FunctionParameterCollectionFactory;
+}
 
 export abstract class RegexParser implements IExpressionParser {
   protected abstract readonly regex: RegExp;
 
+  public constructor(
+    private readonly utilities: RegexParserUtilities = DefaultRegexParserUtilities,
+  ) {
+
+  }
+
   public findExpressions(code: string): IExpression[] {
     return Array.from(this.findRegexExpressions(code));
   }
 
-  protected abstract buildExpression(match: RegExpMatchArray): IPrimitiveExpression;
+  protected abstract buildExpression(match: RegExpMatchArray): PrimitiveExpression;
 
   private* findRegexExpressions(code: string): Iterable<IExpression> {
     if (!code) {
-      throw new Error('missing code');
+      throw new Error(
+        this.buildErrorMessageWithContext({ errorMessage: 'missing code', code: 'EMPTY' }),
+      );
     }
-    const matches = code.matchAll(this.regex);
+    const createErrorContext = (message: string): ErrorContext => ({ code, errorMessage: message });
+    const matches = this.doOrRethrow(
+      () => code.matchAll(this.regex),
+      createErrorContext('Failed to match regex.'),
+    );
     for (const match of matches) {
-      const primitiveExpression = this.buildExpression(match);
-      const position = this.doOrRethrow(() => createPositionFromRegexFullMatch(match), 'invalid script position', code);
-      const parameters = createParameters(primitiveExpression);
-      const expression = new Expression(position, primitiveExpression.evaluator, parameters);
+      const primitiveExpression = this.doOrRethrow(
+        () => this.buildExpression(match),
+        createErrorContext('Failed to build expression.'),
+      );
+      const position = this.doOrRethrow(
+        () => this.utilities.createPosition(match),
+        createErrorContext('Failed to create position.'),
+      );
+      const parameters = this.doOrRethrow(
+        () => createParameters(
+          primitiveExpression,
+          this.utilities.createParameterCollection(),
+        ),
+        createErrorContext('Failed to create parameters.'),
+      );
+      const expression = this.doOrRethrow(
+        () => this.utilities.createExpression({
+          position,
+          evaluator: primitiveExpression.evaluator,
+          parameters,
+        }),
+        createErrorContext('Failed to create expression.'),
+      );
       yield expression;
     }
   }
 
-  private doOrRethrow<T>(action: () => T, errorText: string, code: string): T {
+  private doOrRethrow<T>(
+    action: () => T,
+    context: ErrorContext,
+  ): T {
     try {
       return action();
     } catch (error) {
-      throw new Error(`[${this.constructor.name}] ${errorText}: ${error.message}\nRegex: ${this.regex}\nCode: ${code}`);
+      throw this.utilities.wrapError(
+        error,
+        this.buildErrorMessageWithContext(context),
+      );
     }
   }
+
+  private buildErrorMessageWithContext(context: ErrorContext): string {
+    return [
+      context.errorMessage,
+      `Class name: ${this.constructor.name}`,
+      `Regex pattern used: ${this.regex}`,
+      `Code: ${context.code}`,
+    ].join('\n');
+  }
+}
+
+interface ErrorContext {
+  readonly errorMessage: string,
+  readonly code: string,
 }
 
 function createParameters(
-  expression: IPrimitiveExpression,
-): FunctionParameterCollection {
+  expression: PrimitiveExpression,
+  parameterCollection: IFunctionParameterCollection,
+): IReadOnlyFunctionParameterCollection {
   return (expression.parameters || [])
     .reduce((parameters, parameter) => {
       parameters.addParameter(parameter);
       return parameters;
-    }, new FunctionParameterCollection());
+    }, parameterCollection);
 }
 
-export interface IPrimitiveExpression {
-  evaluator: ExpressionEvaluator;
-  parameters?: readonly IFunctionParameter[];
+export interface PrimitiveExpression {
+  readonly evaluator: ExpressionEvaluator;
+  readonly parameters?: readonly IFunctionParameter[];
 }
+
+export interface ExpressionFactory {
+  (
+    ...args: ConstructorParameters<typeof Expression>
+  ): IExpression;
+}
+
+const DefaultRegexParserUtilities: RegexParserUtilities = {
+  wrapError: wrapErrorWithAdditionalContext,
+  createPosition: createPositionFromRegexFullMatch,
+  createExpression: (...args) => new Expression(...args),
+  createParameterCollection: createFunctionParameterCollection,
+};
diff --git a/src/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser.ts b/src/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser.ts
index 352647506..ef23b1d6a 100644
--- a/src/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser.ts
+++ b/src/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser.ts
@@ -1,5 +1,5 @@
 import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
-import { RegexParser, type IPrimitiveExpression } from '../Parser/Regex/RegexParser';
+import { RegexParser, type PrimitiveExpression } from '../Parser/Regex/RegexParser';
 import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
 
 export class ParameterSubstitutionParser extends RegexParser {
@@ -12,7 +12,7 @@ export class ParameterSubstitutionParser extends RegexParser {
     .expectExpressionEnd()
     .buildRegExp();
 
-  protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
+  protected buildExpression(match: RegExpMatchArray): PrimitiveExpression {
     const parameterName = match[1];
     const pipeline = match[2];
     return {
diff --git a/src/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.ts b/src/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.ts
index cee4f7900..595530032 100644
--- a/src/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.ts
+++ b/src/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.ts
@@ -8,7 +8,7 @@ export class FunctionCallArgument implements IFunctionCallArgument {
   ) {
     ensureValidParameterName(parameterName);
     if (!argumentValue) {
-      throw new Error(`missing argument value for "${parameterName}"`);
+      throw new Error(`Missing argument value for the parameter "${parameterName}".`);
     }
   }
 }
diff --git a/src/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/AdaptiveFunctionCallCompiler.ts b/src/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/AdaptiveFunctionCallCompiler.ts
index e479001de..1ac0f3390 100644
--- a/src/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/AdaptiveFunctionCallCompiler.ts
+++ b/src/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/AdaptiveFunctionCallCompiler.ts
@@ -72,7 +72,7 @@ function throwIfUnexpectedParametersExist(
     // eslint-disable-next-line prefer-template
     `Function "${functionName}" has unexpected parameter(s) provided: `
       + `"${unexpectedParameters.join('", "')}"`
-      + '. Expected parameter(s): '
-      + (expectedParameters.length ? `"${expectedParameters.join('", "')}"` : 'none'),
+      + '.\nExpected parameter(s): '
+      + (expectedParameters.length ? `"${expectedParameters.join('", "')}".` : 'none'),
   );
 }
diff --git a/src/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.ts b/src/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.ts
index 3a70dd49b..5e737eac4 100644
--- a/src/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.ts
+++ b/src/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.ts
@@ -6,11 +6,14 @@ import type { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/
 import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
 import type { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
 import { ParsedFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/ParsedFunctionCall';
+import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
 import type { ArgumentCompiler } from './ArgumentCompiler';
 
 export class NestedFunctionArgumentCompiler implements ArgumentCompiler {
   constructor(
     private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
+    private readonly wrapError: ErrorWithContextWrapper
+    = wrapErrorWithAdditionalContext,
   ) { }
 
   public createCompiledNestedCall(
@@ -22,18 +25,26 @@ export class NestedFunctionArgumentCompiler implements ArgumentCompiler {
       nestedFunction,
       parentFunction.args,
       context,
-      this.expressionsCompiler,
+      {
+        expressionsCompiler: this.expressionsCompiler,
+        wrapError: this.wrapError,
+      },
     );
     const compiledCall = new ParsedFunctionCall(nestedFunction.functionName, compiledArgs);
     return compiledCall;
   }
 }
 
+interface ArgumentCompilationUtilities {
+  readonly expressionsCompiler: IExpressionsCompiler,
+  readonly wrapError: ErrorWithContextWrapper;
+}
+
 function compileNestedFunctionArguments(
   nestedFunction: FunctionCall,
   parentFunctionArgs: IReadOnlyFunctionCallArgumentCollection,
   context: FunctionCallCompilationContext,
-  expressionsCompiler: IExpressionsCompiler,
+  utilities: ArgumentCompilationUtilities,
 ): IReadOnlyFunctionCallArgumentCollection {
   const requiredParameterNames = context
     .allFunctions
@@ -47,7 +58,7 @@ function compileNestedFunctionArguments(
         paramName,
         nestedFunction,
         parentFunctionArgs,
-        expressionsCompiler,
+        utilities,
       ),
     }))
     // Filter out arguments with absent values
@@ -89,13 +100,13 @@ function compileArgument(
   parameterName: string,
   nestedFunction: FunctionCall,
   parentFunctionArgs: IReadOnlyFunctionCallArgumentCollection,
-  expressionsCompiler: IExpressionsCompiler,
+  utilities: ArgumentCompilationUtilities,
 ): string {
   try {
     const { argumentValue: codeInArgument } = nestedFunction.args.getArgument(parameterName);
-    return expressionsCompiler.compileExpressions(codeInArgument, parentFunctionArgs);
-  } catch (err) {
-    throw new AggregateError([err], `Error when compiling argument for "${parameterName}"`);
+    return utilities.expressionsCompiler.compileExpressions(codeInArgument, parentFunctionArgs);
+  } catch (error) {
+    throw utilities.wrapError(error, `Error when compiling argument for "${parameterName}"`);
   }
 }
 
diff --git a/src/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/NestedFunctionCallCompiler.ts b/src/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/NestedFunctionCallCompiler.ts
index 8680f9a08..0c8375cb5 100644
--- a/src/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/NestedFunctionCallCompiler.ts
+++ b/src/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/NestedFunctionCallCompiler.ts
@@ -1,14 +1,21 @@
-import { type CallFunctionBody, FunctionBodyType, type ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
+import {
+  type CallFunctionBody, FunctionBodyType,
+  type ISharedFunction,
+} from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
 import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
 import type { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
 import type { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
+import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
 import { NestedFunctionArgumentCompiler } from './Argument/NestedFunctionArgumentCompiler';
 import type { SingleCallCompilerStrategy } from '../SingleCallCompilerStrategy';
 import type { ArgumentCompiler } from './Argument/ArgumentCompiler';
 
 export class NestedFunctionCallCompiler implements SingleCallCompilerStrategy {
   public constructor(
-    private readonly argumentCompiler: ArgumentCompiler = new NestedFunctionArgumentCompiler(),
+    private readonly argumentCompiler: ArgumentCompiler
+    = new NestedFunctionArgumentCompiler(),
+    private readonly wrapError: ErrorWithContextWrapper
+    = wrapErrorWithAdditionalContext,
   ) {
   }
 
@@ -29,8 +36,11 @@ export class NestedFunctionCallCompiler implements SingleCallCompilerStrategy {
         const compiledNestedCall = context.singleCallCompiler
           .compileSingleCall(compiledParentCall, context);
         return compiledNestedCall;
-      } catch (err) {
-        throw new AggregateError([err], `Error with call to "${nestedCall.functionName}" function from "${callToFunction.functionName}" function`);
+      } catch (error) {
+        throw this.wrapError(
+          error,
+          `Failed to call '${nestedCall.functionName}' (callee function) from '${callToFunction.functionName}' (caller function).`,
+        );
       }
     }).flat();
   }
diff --git a/src/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollectionFactory.ts b/src/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollectionFactory.ts
new file mode 100644
index 000000000..0fd227fd4
--- /dev/null
+++ b/src/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollectionFactory.ts
@@ -0,0 +1,12 @@
+import { FunctionParameterCollection } from './FunctionParameterCollection';
+import type { IFunctionParameterCollection } from './IFunctionParameterCollection';
+
+export interface FunctionParameterCollectionFactory {
+  (
+    ...args: ConstructorParameters<typeof FunctionParameterCollection>
+  ): IFunctionParameterCollection;
+}
+
+export const createFunctionParameterCollection: FunctionParameterCollectionFactory = (...args) => {
+  return new FunctionParameterCollection(...args);
+};
diff --git a/src/application/Parser/Script/Compiler/Function/SharedFunctionCollection.ts b/src/application/Parser/Script/Compiler/Function/SharedFunctionCollection.ts
index c2f6d9b88..17ba56989 100644
--- a/src/application/Parser/Script/Compiler/Function/SharedFunctionCollection.ts
+++ b/src/application/Parser/Script/Compiler/Function/SharedFunctionCollection.ts
@@ -15,7 +15,7 @@ export class SharedFunctionCollection implements ISharedFunctionCollection {
     if (!name) { throw Error('missing function name'); }
     const func = this.functionsByName.get(name);
     if (!func) {
-      throw new Error(`called function is not defined "${name}"`);
+      throw new Error(`Called function is not defined: "${name}"`);
     }
     return func;
   }
diff --git a/src/application/Parser/Script/Compiler/Function/SharedFunctionsParser.ts b/src/application/Parser/Script/Compiler/Function/SharedFunctionsParser.ts
index 9cdb41732..7d2aa345e 100644
--- a/src/application/Parser/Script/Compiler/Function/SharedFunctionsParser.ts
+++ b/src/application/Parser/Script/Compiler/Function/SharedFunctionsParser.ts
@@ -1,5 +1,6 @@
 import type {
-  FunctionData, CodeInstruction, CodeFunctionData, CallFunctionData, CallInstruction,
+  FunctionData, CodeInstruction, CodeFunctionData, CallFunctionData,
+  CallInstruction, ParameterDefinitionData,
 } from '@/application/collections/';
 import type { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syntax/ILanguageSyntax';
 import { CodeValidator } from '@/application/Parser/Script/Validation/CodeValidator';
@@ -7,20 +8,30 @@ import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmp
 import { NoDuplicatedLines } from '@/application/Parser/Script/Validation/Rules/NoDuplicatedLines';
 import type { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
 import { isArray, isNullOrUndefined, isPlainObject } from '@/TypeHelpers';
+import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
 import { createFunctionWithInlineCode, createCallerFunction } from './SharedFunction';
 import { SharedFunctionCollection } from './SharedFunctionCollection';
 import { FunctionParameter } from './Parameter/FunctionParameter';
-import { FunctionParameterCollection } from './Parameter/FunctionParameterCollection';
 import { parseFunctionCalls } from './Call/FunctionCallParser';
+import { createFunctionParameterCollection, type FunctionParameterCollectionFactory } from './Parameter/FunctionParameterCollectionFactory';
 import type { ISharedFunctionCollection } from './ISharedFunctionCollection';
 import type { ISharedFunctionsParser } from './ISharedFunctionsParser';
 import type { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
 import type { ISharedFunction } from './ISharedFunction';
 
+const DefaultSharedFunctionsParsingUtilities: SharedFunctionsParsingUtilities = {
+  wrapError: wrapErrorWithAdditionalContext,
+  createParameter: (...args) => new FunctionParameter(...args),
+  codeValidator: CodeValidator.instance,
+  createParameterCollection: createFunctionParameterCollection,
+};
+
 export class SharedFunctionsParser implements ISharedFunctionsParser {
   public static readonly instance: ISharedFunctionsParser = new SharedFunctionsParser();
 
-  constructor(private readonly codeValidator: ICodeValidator = CodeValidator.instance) { }
+  constructor(
+    private readonly utilities = DefaultSharedFunctionsParsingUtilities,
+  ) { }
 
   public parseFunctions(
     functions: readonly FunctionData[],
@@ -32,7 +43,7 @@ export class SharedFunctionsParser implements ISharedFunctionsParser {
     }
     ensureValidFunctions(functions);
     return functions
-      .map((func) => parseFunction(func, syntax, this.codeValidator))
+      .map((func) => parseFunction(func, syntax, this.utilities))
       .reduce((acc, func) => {
         acc.addFunction(func);
         return acc;
@@ -40,15 +51,26 @@ export class SharedFunctionsParser implements ISharedFunctionsParser {
   }
 }
 
+interface SharedFunctionsParsingUtilities {
+  readonly wrapError: ErrorWithContextWrapper;
+  readonly createParameter: FunctionParameterFactory;
+  readonly codeValidator: ICodeValidator;
+  readonly createParameterCollection: FunctionParameterCollectionFactory;
+}
+
+export type FunctionParameterFactory = (
+  ...args: ConstructorParameters<typeof FunctionParameter>
+) => FunctionParameter;
+
 function parseFunction(
   data: FunctionData,
   syntax: ILanguageSyntax,
-  validator: ICodeValidator,
+  utilities: SharedFunctionsParsingUtilities,
 ): ISharedFunction {
   const { name } = data;
-  const parameters = parseParameters(data);
+  const parameters = parseParameters(data, utilities);
   if (hasCode(data)) {
-    validateCode(data, syntax, validator);
+    validateCode(data, syntax, utilities.codeValidator);
     return createFunctionWithInlineCode(name, parameters, data.code, data.revertCode);
   }
   // Has call
@@ -71,22 +93,38 @@ function validateCode(
     );
 }
 
-function parseParameters(data: FunctionData): IReadOnlyFunctionParameterCollection {
+function parseParameters(
+  data: FunctionData,
+  utilities: SharedFunctionsParsingUtilities,
+): IReadOnlyFunctionParameterCollection {
   return (data.parameters || [])
-    .map((parameter) => {
-      try {
-        return new FunctionParameter(
-          parameter.name,
-          parameter.optional || false,
-        );
-      } catch (err) {
-        throw new Error(`"${data.name}": ${err.message}`);
-      }
-    })
+    .map((parameter) => createFunctionParameter(
+      data.name,
+      parameter,
+      utilities,
+    ))
     .reduce((parameters, parameter) => {
       parameters.addParameter(parameter);
       return parameters;
-    }, new FunctionParameterCollection());
+    }, utilities.createParameterCollection());
+}
+
+function createFunctionParameter(
+  functionName: string,
+  parameterData: ParameterDefinitionData,
+  utilities: SharedFunctionsParsingUtilities,
+): FunctionParameter {
+  try {
+    return utilities.createParameter(
+      parameterData.name,
+      parameterData.optional || false,
+    );
+  } catch (err) {
+    throw utilities.wrapError(
+      err,
+      `Failed to create parameter: ${parameterData.name} for function "${functionName}"`,
+    );
+  }
 }
 
 function hasCode(data: FunctionData): data is CodeFunctionData {
diff --git a/src/application/Parser/Script/Compiler/ScriptCompiler.ts b/src/application/Parser/Script/Compiler/ScriptCompiler.ts
index 2e516118d..84fbdcb8a 100644
--- a/src/application/Parser/Script/Compiler/ScriptCompiler.ts
+++ b/src/application/Parser/Script/Compiler/ScriptCompiler.ts
@@ -1,10 +1,11 @@
 import type { FunctionData, ScriptData, CallInstruction } from '@/application/collections/';
 import type { IScriptCode } from '@/domain/IScriptCode';
-import { ScriptCode } from '@/domain/ScriptCode';
 import type { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syntax/ILanguageSyntax';
 import { CodeValidator } from '@/application/Parser/Script/Validation/CodeValidator';
 import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmptyLines';
 import type { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
+import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
+import { createScriptCode, type ScriptCodeFactory } from '@/domain/ScriptCodeFactory';
 import { SharedFunctionsParser } from './Function/SharedFunctionsParser';
 import { FunctionCallSequenceCompiler } from './Function/Call/Compiler/FunctionCallSequenceCompiler';
 import { parseFunctionCalls } from './Function/Call/FunctionCallParser';
@@ -23,6 +24,8 @@ export class ScriptCompiler implements IScriptCompiler {
     sharedFunctionsParser: ISharedFunctionsParser = SharedFunctionsParser.instance,
     private readonly callCompiler: FunctionCallCompiler = FunctionCallSequenceCompiler.instance,
     private readonly codeValidator: ICodeValidator = CodeValidator.instance,
+    private readonly wrapError: ErrorWithContextWrapper = wrapErrorWithAdditionalContext,
+    private readonly scriptCodeFactory: ScriptCodeFactory = createScriptCode,
   ) {
     this.functions = sharedFunctionsParser.parseFunctions(functions, syntax);
   }
@@ -39,12 +42,12 @@ export class ScriptCompiler implements IScriptCompiler {
       const calls = parseFunctionCalls(script.call);
       const compiledCode = this.callCompiler.compileFunctionCalls(calls, this.functions);
       validateCompiledCode(compiledCode, this.codeValidator);
-      return new ScriptCode(
+      return this.scriptCodeFactory(
         compiledCode.code,
         compiledCode.revertCode,
       );
     } catch (error) {
-      throw Error(`Script "${script.name}" ${error.message}`);
+      throw this.wrapError(error, `Failed to compile script: ${script.name}`);
     }
   }
 }
diff --git a/src/application/Parser/Script/ScriptParser.ts b/src/application/Parser/Script/ScriptParser.ts
index 9db5c64be..bc3ebc5a1 100644
--- a/src/application/Parser/Script/ScriptParser.ts
+++ b/src/application/Parser/Script/ScriptParser.ts
@@ -4,37 +4,52 @@ import type { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syn
 import { Script } from '@/domain/Script';
 import { RecommendationLevel } from '@/domain/RecommendationLevel';
 import type { IScriptCode } from '@/domain/IScriptCode';
-import { ScriptCode } from '@/domain/ScriptCode';
 import type { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
-import { parseDocs } from '../DocumentationParser';
+import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
+import type { ScriptCodeFactory } from '@/domain/ScriptCodeFactory';
+import { createScriptCode } from '@/domain/ScriptCodeFactory';
+import type { IScript } from '@/domain/IScript';
+import { parseDocs, type DocsParser } from '../DocumentationParser';
 import { createEnumParser, type IEnumParser } from '../../Common/Enum';
-import { NodeType } from '../NodeValidation/NodeType';
-import { NodeValidator } from '../NodeValidation/NodeValidator';
+import { NodeDataType } from '../NodeValidation/NodeDataType';
+import { createNodeDataValidator, type NodeDataValidator, type NodeDataValidatorFactory } from '../NodeValidation/NodeDataValidator';
 import { CodeValidator } from './Validation/CodeValidator';
 import { NoDuplicatedLines } from './Validation/Rules/NoDuplicatedLines';
 import type { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';
 
-export function parseScript(
-  data: ScriptData,
-  context: ICategoryCollectionParseContext,
-  levelParser = createEnumParser(RecommendationLevel),
-  scriptFactory: ScriptFactoryType = ScriptFactory,
-  codeValidator: ICodeValidator = CodeValidator.instance,
-): Script {
-  const validator = new NodeValidator({ type: NodeType.Script, selfNode: data });
+export interface ScriptParser {
+  (
+    data: ScriptData,
+    context: ICategoryCollectionParseContext,
+    utilities?: ScriptParserUtilities,
+  ): IScript;
+}
+
+export const parseScript: ScriptParser = (
+  data,
+  context,
+  utilities = DefaultScriptParserUtilities,
+) => {
+  const validator = utilities.createValidator({
+    type: NodeDataType.Script,
+    selfNode: data,
+  });
   validateScript(data, validator);
   try {
-    const script = scriptFactory(
-      /* name: */ data.name,
-      /* code: */ parseCode(data, context, codeValidator),
-      /* docs: */ parseDocs(data),
-      /* level: */ parseLevel(data.recommend, levelParser),
-    );
+    const script = utilities.createScript({
+      name: data.name,
+      code: parseCode(data, context, utilities.codeValidator, utilities.createCode),
+      docs: utilities.parseDocs(data),
+      level: parseLevel(data.recommend, utilities.levelParser),
+    });
     return script;
-  } catch (err) {
-    return validator.throw(err.message);
+  } catch (error) {
+    throw utilities.wrapError(
+      error,
+      validator.createContextualErrorMessage('Failed to parse script.'),
+    );
   }
-}
+};
 
 function parseLevel(
   level: string | undefined,
@@ -50,18 +65,19 @@ function parseCode(
   script: ScriptData,
   context: ICategoryCollectionParseContext,
   codeValidator: ICodeValidator,
+  createCode: ScriptCodeFactory,
 ): IScriptCode {
   if (context.compiler.canCompile(script)) {
     return context.compiler.compile(script);
   }
   const codeScript = script as CodeScriptData; // Must be inline code if it cannot be compiled
-  const code = new ScriptCode(codeScript.code, codeScript.revertCode);
+  const code = createCode(codeScript.code, codeScript.revertCode);
   validateHardcodedCodeWithoutCalls(code, codeValidator, context.syntax);
   return code;
 }
 
 function validateHardcodedCodeWithoutCalls(
-  scriptCode: ScriptCode,
+  scriptCode: IScriptCode,
   validator: ICodeValidator,
   syntax: ILanguageSyntax,
 ) {
@@ -77,25 +93,48 @@ function validateHardcodedCodeWithoutCalls(
 
 function validateScript(
   script: ScriptData,
-  validator: NodeValidator,
+  validator: NodeDataValidator,
 ): asserts script is NonNullable<ScriptData> {
-  validator
-    .assertDefined(script)
-    .assertValidName(script.name)
-    .assert(
-      () => Boolean((script as CodeScriptData).code || (script as CallScriptData).call),
-      'Neither "call" or "code" is defined.',
-    )
-    .assert(
-      () => !((script as CodeScriptData).code && (script as CallScriptData).call),
-      'Both "call" and "code" are defined.',
-    )
-    .assert(
-      () => !((script as CodeScriptData).revertCode && (script as CallScriptData).call),
-      'Both "call" and "revertCode" are defined.',
-    );
+  validator.assertDefined(script);
+  validator.assertValidName(script.name);
+  validator.assert(
+    () => Boolean((script as CodeScriptData).code || (script as CallScriptData).call),
+    'Neither "call" or "code" is defined.',
+  );
+  validator.assert(
+    () => !((script as CodeScriptData).code && (script as CallScriptData).call),
+    'Both "call" and "code" are defined.',
+  );
+  validator.assert(
+    () => !((script as CodeScriptData).revertCode && (script as CallScriptData).call),
+    'Both "call" and "revertCode" are defined.',
+  );
 }
 
-export type ScriptFactoryType = (...parameters: ConstructorParameters<typeof Script>) => Script;
+interface ScriptParserUtilities {
+  readonly levelParser: IEnumParser<RecommendationLevel>;
+  readonly createScript: ScriptFactory;
+  readonly codeValidator: ICodeValidator;
+  readonly wrapError: ErrorWithContextWrapper;
+  readonly createValidator: NodeDataValidatorFactory;
+  readonly createCode: ScriptCodeFactory;
+  readonly parseDocs: DocsParser;
+}
+
+export type ScriptFactory = (
+  ...parameters: ConstructorParameters<typeof Script>
+) => IScript;
+
+const createScript: ScriptFactory = (...parameters) => {
+  return new Script(...parameters);
+};
 
-const ScriptFactory: ScriptFactoryType = (...parameters) => new Script(...parameters);
+const DefaultScriptParserUtilities: ScriptParserUtilities = {
+  levelParser: createEnumParser(RecommendationLevel),
+  createScript,
+  codeValidator: CodeValidator.instance,
+  wrapError: wrapErrorWithAdditionalContext,
+  createValidator: createNodeDataValidator,
+  createCode: createScriptCode,
+  parseDocs,
+};
diff --git a/src/application/Parser/ScriptingDefinition/CodeSubstituter.ts b/src/application/Parser/ScriptingDefinition/CodeSubstituter.ts
index 82ef30107..fc7aa24ff 100644
--- a/src/application/Parser/ScriptingDefinition/CodeSubstituter.ts
+++ b/src/application/Parser/ScriptingDefinition/CodeSubstituter.ts
@@ -5,6 +5,7 @@ import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expres
 import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
 import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
 import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
+import type { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
 import type { ICodeSubstituter } from './ICodeSubstituter';
 
 export class CodeSubstituter implements ICodeSubstituter {
@@ -29,7 +30,9 @@ export class CodeSubstituter implements ICodeSubstituter {
 }
 
 function createSubstituteCompiler(): IExpressionsCompiler {
-  const parsers = [new ParameterSubstitutionParser()];
+  const parsers: readonly IExpressionParser[] = [
+    new ParameterSubstitutionParser(),
+  ] as const;
   const parser = new CompositeExpressionParser(parsers);
   const expressionCompiler = new ExpressionsCompiler(parser);
   return expressionCompiler;
diff --git a/src/domain/Category.ts b/src/domain/Category.ts
index 38e68b1f1..b325b20d4 100644
--- a/src/domain/Category.ts
+++ b/src/domain/Category.ts
@@ -5,15 +5,21 @@ import type { IScript } from './IScript';
 export class Category extends BaseEntity<number> implements ICategory {
   private allSubScripts?: ReadonlyArray<IScript> = undefined;
 
-  constructor(
-    id: number,
-    public readonly name: string,
-    public readonly docs: ReadonlyArray<string>,
-    public readonly subCategories: ReadonlyArray<ICategory>,
-    public readonly scripts: ReadonlyArray<IScript>,
-  ) {
-    super(id);
-    validateCategory(this);
+  public readonly name: string;
+
+  public readonly docs: ReadonlyArray<string>;
+
+  public readonly subCategories: ReadonlyArray<ICategory>;
+
+  public readonly scripts: ReadonlyArray<IScript>;
+
+  constructor(parameters: CategoryInitParameters) {
+    super(parameters.id);
+    validateParameters(parameters);
+    this.name = parameters.name;
+    this.docs = parameters.docs;
+    this.subCategories = parameters.subcategories;
+    this.scripts = parameters.scripts;
   }
 
   public includes(script: IScript): boolean {
@@ -28,6 +34,14 @@ export class Category extends BaseEntity<number> implements ICategory {
   }
 }
 
+export interface CategoryInitParameters {
+  readonly id: number;
+  readonly name: string;
+  readonly docs: ReadonlyArray<string>;
+  readonly subcategories: ReadonlyArray<ICategory>;
+  readonly scripts: ReadonlyArray<IScript>;
+}
+
 function parseScriptsRecursively(category: ICategory): ReadonlyArray<IScript> {
   return [
     ...category.scripts,
@@ -35,11 +49,11 @@ function parseScriptsRecursively(category: ICategory): ReadonlyArray<IScript> {
   ];
 }
 
-function validateCategory(category: ICategory) {
-  if (!category.name) {
+function validateParameters(parameters: CategoryInitParameters) {
+  if (!parameters.name) {
     throw new Error('missing name');
   }
-  if (category.subCategories.length === 0 && category.scripts.length === 0) {
+  if (parameters.subcategories.length === 0 && parameters.scripts.length === 0) {
     throw new Error('A category must have at least one sub-category or script');
   }
 }
diff --git a/src/domain/Script.ts b/src/domain/Script.ts
index 79ba3cfb8..7440aa4c0 100644
--- a/src/domain/Script.ts
+++ b/src/domain/Script.ts
@@ -4,14 +4,21 @@ import type { IScript } from './IScript';
 import type { IScriptCode } from './IScriptCode';
 
 export class Script extends BaseEntity<string> implements IScript {
-  constructor(
-    public readonly name: string,
-    public readonly code: IScriptCode,
-    public readonly docs: ReadonlyArray<string>,
-    public readonly level?: RecommendationLevel,
-  ) {
-    super(name);
-    validateLevel(level);
+  public readonly name: string;
+
+  public readonly code: IScriptCode;
+
+  public readonly docs: ReadonlyArray<string>;
+
+  public readonly level?: RecommendationLevel;
+
+  constructor(parameters: ScriptInitParameters) {
+    super(parameters.name);
+    this.name = parameters.name;
+    this.code = parameters.code;
+    this.docs = parameters.docs;
+    this.level = parameters.level;
+    validateLevel(parameters.level);
   }
 
   public canRevert(): boolean {
@@ -19,6 +26,13 @@ export class Script extends BaseEntity<string> implements IScript {
   }
 }
 
+export interface ScriptInitParameters {
+  readonly name: string;
+  readonly code: IScriptCode;
+  readonly docs: ReadonlyArray<string>;
+  readonly level?: RecommendationLevel;
+}
+
 function validateLevel(level?: RecommendationLevel) {
   if (level !== undefined && !(level in RecommendationLevel)) {
     throw new Error(`invalid level: ${level}`);
diff --git a/src/domain/ScriptCodeFactory.ts b/src/domain/ScriptCodeFactory.ts
new file mode 100644
index 000000000..303a4df79
--- /dev/null
+++ b/src/domain/ScriptCodeFactory.ts
@@ -0,0 +1,10 @@
+import { ScriptCode } from './ScriptCode';
+import type { IScriptCode } from './IScriptCode';
+
+export interface ScriptCodeFactory {
+  (
+    ...args: ConstructorParameters<typeof ScriptCode>
+  ): IScriptCode;
+}
+
+export const createScriptCode: ScriptCodeFactory = (...args) => new ScriptCode(...args);
diff --git a/tests/unit/application/Parser/CategoryParser.spec.ts b/tests/unit/application/Parser/CategoryParser.spec.ts
index f7dd352e4..e1de049f5 100644
--- a/tests/unit/application/Parser/CategoryParser.spec.ts
+++ b/tests/unit/application/Parser/CategoryParser.spec.ts
@@ -1,258 +1,395 @@
 import { describe, it, expect } from 'vitest';
 import type { CategoryData, CategoryOrScriptData } from '@/application/collections/';
-import { type CategoryFactoryType, parseCategory } from '@/application/Parser/CategoryParser';
-import { parseScript } from '@/application/Parser/Script/ScriptParser';
-import { parseDocs } from '@/application/Parser/DocumentationParser';
-import { ScriptCompilerStub } from '@tests/unit/shared/Stubs/ScriptCompilerStub';
+import { type CategoryFactory, parseCategory } from '@/application/Parser/CategoryParser';
+import { type ScriptParser } from '@/application/Parser/Script/ScriptParser';
+import { type DocsParser } from '@/application/Parser/DocumentationParser';
 import { CategoryCollectionParseContextStub } from '@tests/unit/shared/Stubs/CategoryCollectionParseContextStub';
-import { LanguageSyntaxStub } from '@tests/unit/shared/Stubs/LanguageSyntaxStub';
 import { CategoryDataStub } from '@tests/unit/shared/Stubs/CategoryDataStub';
-import { itEachAbsentCollectionValue } from '@tests/unit/shared/TestCases/AbsentTests';
-import { NodeType } from '@/application/Parser/NodeValidation/NodeType';
-import { expectThrowsNodeError, type ITestScenario, NodeValidationTestRunner } from '@tests/unit/application/Parser/NodeValidation/NodeValidatorTestRunner';
+import { getAbsentCollectionTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
+import { NodeDataType } from '@/application/Parser/NodeValidation/NodeDataType';
 import type { ICategoryCollectionParseContext } from '@/application/Parser/Script/ICategoryCollectionParseContext';
-import { Category } from '@/domain/Category';
-import { createScriptDataWithCall, createScriptDataWithCode, createScriptDataWithoutCallOrCodes } from '@tests/unit/shared/Stubs/ScriptDataStub';
+import { createScriptDataWithCall, createScriptDataWithCode } from '@tests/unit/shared/Stubs/ScriptDataStub';
+import type { ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
+import { ErrorWrapperStub } from '@tests/unit/shared/Stubs/ErrorWrapperStub';
+import type { NodeDataValidatorFactory } from '@/application/Parser/NodeValidation/NodeDataValidator';
+import { NodeDataValidatorStub, createNodeDataValidatorFactoryStub } from '@tests/unit/shared/Stubs/NodeDataValidatorStub';
+import type { CategoryNodeErrorContext, UnknownNodeErrorContext } from '@/application/Parser/NodeValidation/NodeDataErrorContext';
+import { CategoryStub } from '@tests/unit/shared/Stubs/CategoryStub';
+import { createCategoryFactorySpy } from '@tests/unit/shared/Stubs/CategoryFactoryStub';
+import { expectExists } from '@tests/shared/Assertions/ExpectExists';
+import { ScriptStub } from '@tests/unit/shared/Stubs/ScriptStub';
+import { ScriptParserStub } from '@tests/unit/shared/Stubs/ScriptParserStub';
+import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
+import { indentText } from '@tests/shared/Text';
+import { itThrowsContextualError } from './ContextualErrorTester';
+import { itValidatesName, itValidatesDefinedData, itAsserts } from './NodeDataValidationTester';
+import { generateDataValidationTestScenarios } from './DataValidationTestScenarioGenerator';
 
 describe('CategoryParser', () => {
   describe('parseCategory', () => {
-    describe('invalid category data', () => {
-      describe('validates script data', () => {
-        describe('satisfies shared node tests', () => {
-          new NodeValidationTestRunner()
-            .testInvalidNodeName((invalidName) => {
-              return createTest(
-                new CategoryDataStub().withName(invalidName),
-              );
-            })
-            .testMissingNodeData((node) => {
-              return createTest(node as CategoryData);
-            });
+    describe('validation', () => {
+      describe('validates for name', () => {
+        // arrange
+        const expectedName = 'expected category name to be validated';
+        const category = new CategoryDataStub()
+          .withName(expectedName);
+        const expectedContext: CategoryNodeErrorContext = {
+          type: NodeDataType.Category,
+          selfNode: category,
+        };
+        itValidatesName((validatorFactory) => {
+          // act
+          new TestBuilder()
+            .withData(category)
+            .withValidatorFactory(validatorFactory)
+            .parseCategory();
+          // assert
+          return {
+            expectedNameToValidate: expectedName,
+            expectedErrorContext: expectedContext,
+          };
         });
-        describe('throws when category children is absent', () => {
-          itEachAbsentCollectionValue<CategoryOrScriptData>((absentValue) => {
-            // arrange
-            const categoryName = 'test';
-            const expectedMessage = `"${categoryName}" has no children.`;
-            const category = new CategoryDataStub()
-              .withName(categoryName)
-              .withChildren(absentValue);
+      });
+      describe('validates for defined data', () => {
+        // arrange
+        const category = new CategoryDataStub();
+        const expectedContext: CategoryNodeErrorContext = {
+          type: NodeDataType.Category,
+          selfNode: category,
+        };
+        itValidatesDefinedData(
+          (validatorFactory) => {
             // act
-            const test = createTest(category);
+            new TestBuilder()
+              .withData(category)
+              .withValidatorFactory(validatorFactory)
+              .parseCategory();
             // assert
-            expectThrowsNodeError(test, expectedMessage);
-          }, { excludeUndefined: true, excludeNull: true });
+            return {
+              expectedDataToValidate: category,
+              expectedErrorContext: expectedContext,
+            };
+          },
+        );
+      });
+      describe('validates that category has some children', () => {
+        const categoryName = 'test';
+        const testScenarios = generateDataValidationTestScenarios<CategoryData>({
+          expectFail: getAbsentCollectionTestCases<CategoryOrScriptData>().map(({
+            valueName, absentValue: absentCollectionValue,
+          }) => ({
+            description: `with \`${valueName}\` value as children`,
+            data: new CategoryDataStub()
+              .withName(categoryName)
+              .withChildren(absentCollectionValue as unknown as CategoryOrScriptData[]),
+          })),
+          expectPass: [{
+            description: 'has single children',
+            data: new CategoryDataStub()
+              .withName(categoryName)
+              .withChildren([createScriptDataWithCode()]),
+          }],
         });
-        describe('throws when category child is missing', () => {
-          new NodeValidationTestRunner()
-            .testMissingNodeData((missingNode) => {
-              // arrange
-              const invalidChildNode = missingNode;
-              const parent = new CategoryDataStub()
-                .withName('parent')
-                .withChildren([new CategoryDataStub().withName('valid child'), invalidChildNode]);
-              return ({
+        testScenarios.forEach(({
+          description, expectedPass, data: categoryData,
+        }) => {
+          describe(description, () => {
+            itAsserts({
+              expectedConditionResult: expectedPass,
+              test: (validatorFactory) => {
+                const expectedMessage = `"${categoryName}" has no children.`;
+                const expectedContext: CategoryNodeErrorContext = {
+                  type: NodeDataType.Category,
+                  selfNode: categoryData,
+                };
                 // act
-                act: () => new TestBuilder()
-                  .withData(parent)
-                  .parseCategory(),
+                try {
+                  new TestBuilder()
+                    .withData(categoryData)
+                    .withValidatorFactory(validatorFactory)
+                    .parseCategory();
+                } catch { /* It may throw due to assertions not being evaluated */ }
                 // assert
-                expectedContext: {
-                  selfNode: invalidChildNode,
+                return {
+                  expectedErrorMessage: expectedMessage,
+                  expectedErrorContext: expectedContext,
+                };
+              },
+            });
+          });
+        });
+      });
+      describe('validates that a child is a category or a script', () => {
+        // arrange
+        const testScenarios = generateDataValidationTestScenarios<CategoryOrScriptData>({
+          expectFail: [{
+            description: 'child has incorrect properties',
+            data: { property: 'non-empty-value' } as unknown as CategoryOrScriptData,
+          }],
+          expectPass: [
+            {
+              description: 'child is a category',
+              data: new CategoryDataStub(),
+            },
+            {
+              description: 'child is a script with call',
+              data: createScriptDataWithCall(),
+            },
+            {
+              description: 'child is a script with code',
+              data: createScriptDataWithCode(),
+            },
+          ],
+        });
+        testScenarios.forEach(({
+          description, expectedPass, data: childData,
+        }) => {
+          describe(description, () => {
+            itAsserts({
+              expectedConditionResult: expectedPass,
+              test: (validatorFactory) => {
+                const expectedError = 'Node is neither a category or a script.';
+                const parent = new CategoryDataStub()
+                  .withName('parent')
+                  .withChildren([new CategoryDataStub().withName('valid child'), childData]);
+                const expectedContext: UnknownNodeErrorContext = {
+                  selfNode: childData,
                   parentNode: parent,
-                },
-              });
+                };
+                // act
+                new TestBuilder()
+                  .withData(parent)
+                  .withValidatorFactory(validatorFactory)
+                  .parseCategory();
+                // assert
+                return {
+                  expectedErrorMessage: expectedError,
+                  expectedErrorContext: expectedContext,
+                };
+              },
             });
+          });
         });
-        it('throws when node is neither a category or a script', () => {
+      });
+      describe('validates children recursively', () => {
+        describe('validates (1th-level) child data', () => {
           // arrange
-          const expectedError = 'Node is neither a category or a script.';
-          const invalidChildNode = { property: 'non-empty-value' } as never as CategoryOrScriptData;
+          const expectedName = 'child category';
+          const child = new CategoryDataStub()
+            .withName(expectedName);
           const parent = new CategoryDataStub()
             .withName('parent')
-            .withChildren([new CategoryDataStub().withName('valid child'), invalidChildNode]);
-          // act
-          const test: ITestScenario = {
-            // act
-            act: () => new TestBuilder()
-              .withData(parent)
-              .parseCategory(),
-            // assert
-            expectedContext: {
-              selfNode: invalidChildNode,
-              parentNode: parent,
-            },
+            .withChildren([child]);
+          const expectedContext: UnknownNodeErrorContext = {
+            selfNode: child,
+            parentNode: parent,
           };
-          // assert
-          expectThrowsNodeError(test, expectedError);
-        });
-        describe('throws when category child is invalid category', () => {
-          new NodeValidationTestRunner().testInvalidNodeName((invalidName) => {
-            // arrange
-            const invalidChildNode = new CategoryDataStub()
-              .withName(invalidName);
-            const parent = new CategoryDataStub()
-              .withName('parent')
-              .withChildren([new CategoryDataStub().withName('valid child'), invalidChildNode]);
-            return ({
+          itValidatesDefinedData(
+            (validatorFactory) => {
               // act
-              act: () => new TestBuilder()
+              new TestBuilder()
                 .withData(parent)
-                .parseCategory(),
+                .withValidatorFactory(validatorFactory)
+                .parseCategory();
               // assert
-              expectedContext: {
-                type: NodeType.Category,
-                selfNode: invalidChildNode,
-                parentNode: parent,
-              },
-            });
-          });
-        });
-        function createTest(category: CategoryData): ITestScenario {
-          return {
-            act: () => new TestBuilder()
-              .withData(category)
-              .parseCategory(),
-            expectedContext: {
-              type: NodeType.Category,
-              selfNode: category,
+              return {
+                expectedDataToValidate: child,
+                expectedErrorContext: expectedContext,
+              };
             },
+          );
+        });
+        describe('validates that (2nd-level) child name', () => {
+          // arrange
+          const expectedName = 'grandchild category';
+          const grandChild = new CategoryDataStub()
+            .withName(expectedName);
+          const child = new CategoryDataStub()
+            .withChildren([grandChild])
+            .withName('child category');
+          const parent = new CategoryDataStub()
+            .withName('parent')
+            .withChildren([child]);
+          const expectedContext: CategoryNodeErrorContext = {
+            type: NodeDataType.Category,
+            selfNode: grandChild,
+            parentNode: child,
           };
-        }
+          itValidatesName((validatorFactory) => {
+            // act
+            new TestBuilder()
+              .withData(parent)
+              .withValidatorFactory(validatorFactory)
+              .parseCategory();
+            // assert
+            return {
+              expectedNameToValidate: expectedName,
+              expectedErrorContext: expectedContext,
+            };
+          });
+        });
       });
-      it(`rethrows exception if ${Category.name} cannot be constructed`, () => {
-        // arrange
-        const expectedError = 'category creation failed';
-        const factoryMock: CategoryFactoryType = () => { throw new Error(expectedError); };
-        const data = new CategoryDataStub();
-        // act
-        const act = () => new TestBuilder()
-          .withData(data)
-          .withFactory(factoryMock)
-          .parseCategory();
-        // expect
-        expectThrowsNodeError({
-          act,
-          expectedContext: {
-            type: NodeType.Category,
-            selfNode: data,
-          },
-        }, expectedError);
+    });
+    describe('rethrows exception if category factory fails', () => {
+      // arrange
+      const givenData = new CategoryDataStub();
+      const expectedContextMessage = 'Failed to parse category.';
+      const expectedError = new Error();
+      // act & assert
+      itThrowsContextualError({
+        throwingAction: (wrapError) => {
+          const validatorStub = new NodeDataValidatorStub();
+          validatorStub.createContextualErrorMessage = (message) => message;
+          const factoryMock: CategoryFactory = () => {
+            throw expectedError;
+          };
+          new TestBuilder()
+            .withCategoryFactory(factoryMock)
+            .withValidatorFactory(() => validatorStub)
+            .withErrorWrapper(wrapError)
+            .withData(givenData)
+            .parseCategory();
+        },
+        expectedWrappedError: expectedError,
+        expectedContextMessage,
       });
     });
-    it('returns expected docs', () => {
+    it('parses docs correctly', () => {
       // arrange
       const url = 'https://privacy.sexy';
-      const expected = parseDocs({ docs: url });
-      const category = new CategoryDataStub()
+      const categoryData = new CategoryDataStub()
         .withDocs(url);
+      const parseDocs: DocsParser = (data) => {
+        return [
+          `parsed docs: ${JSON.stringify(data)}`,
+        ];
+      };
+      const expectedDocs = parseDocs(categoryData);
+      const { categoryFactorySpy, getInitParameters } = createCategoryFactorySpy();
       // act
-      const actual = new TestBuilder()
-        .withData(category)
-        .parseCategory()
-        .docs;
+      const actualCategory = new TestBuilder()
+        .withData(categoryData)
+        .withCategoryFactory(categoryFactorySpy)
+        .withDocsParser(parseDocs)
+        .parseCategory();
       // assert
-      expect(actual).to.deep.equal(expected);
+      const actualDocs = getInitParameters(actualCategory)?.docs;
+      expect(actualDocs).to.deep.equal(expectedDocs);
     });
     describe('parses expected subscript', () => {
-      it('single script with code', () => {
+      it('parses single script correctly', () => {
         // arrange
-        const script = createScriptDataWithCode();
-        const context = new CategoryCollectionParseContextStub();
-        const expected = [parseScript(script, context)];
-        const category = new CategoryDataStub()
-          .withChildren([script]);
-        // act
-        const actual = new TestBuilder()
-          .withData(category)
-          .withContext(context)
-          .parseCategory()
-          .scripts;
-        // assert
-        expect(actual).to.deep.equal(expected);
-      });
-      it('single script with function call', () => {
-        // arrange
-        const script = createScriptDataWithCall();
-        const compiler = new ScriptCompilerStub()
-          .withCompileAbility(script);
-        const context = new CategoryCollectionParseContextStub()
-          .withCompiler(compiler);
-        const expected = [parseScript(script, context)];
-        const category = new CategoryDataStub()
-          .withChildren([script]);
+        const expectedScript = new ScriptStub('expected script');
+        const scriptParser = new ScriptParserStub();
+        const childScriptData = createScriptDataWithCode();
+        const categoryData = new CategoryDataStub()
+          .withChildren([childScriptData]);
+        scriptParser.setupParsedResultForData(childScriptData, expectedScript);
+        const { categoryFactorySpy, getInitParameters } = createCategoryFactorySpy();
         // act
-        const actual = new TestBuilder()
-          .withData(category)
-          .withContext(context)
-          .parseCategory()
-          .scripts;
+        const actualCategory = new TestBuilder()
+          .withData(categoryData)
+          .withScriptParser(scriptParser.get())
+          .withCategoryFactory(categoryFactorySpy)
+          .parseCategory();
         // assert
-        expect(actual).to.deep.equal(expected);
+        const actualScripts = getInitParameters(actualCategory)?.scripts;
+        expectExists(actualScripts);
+        expect(actualScripts).to.have.lengthOf(1);
+        const actualScript = actualScripts[0];
+        expect(actualScript).to.equal(expectedScript);
       });
-      it('multiple scripts with function call and code', () => {
+      it('parses multiple scripts correctly', () => {
         // arrange
-        const callableScript = createScriptDataWithCall();
-        const scripts = [callableScript, createScriptDataWithCode()];
-        const category = new CategoryDataStub()
-          .withChildren(scripts);
-        const compiler = new ScriptCompilerStub()
-          .withCompileAbility(callableScript);
-        const context = new CategoryCollectionParseContextStub()
-          .withCompiler(compiler);
-        const expected = scripts.map((script) => parseScript(script, context));
+        const expectedScripts = [
+          new ScriptStub('expected-first-script'),
+          new ScriptStub('expected-second-script'),
+        ];
+        const childrenData = [
+          createScriptDataWithCall(),
+          createScriptDataWithCode(),
+        ];
+        const scriptParser = new ScriptParserStub();
+        childrenData.forEach((_, index) => {
+          scriptParser.setupParsedResultForData(childrenData[index], expectedScripts[index]);
+        });
+        const categoryData = new CategoryDataStub()
+          .withChildren(childrenData);
+        const { categoryFactorySpy, getInitParameters } = createCategoryFactorySpy();
         // act
-        const actual = new TestBuilder()
-          .withData(category)
-          .withContext(context)
-          .parseCategory()
-          .scripts;
+        const actualCategory = new TestBuilder()
+          .withScriptParser(scriptParser.get())
+          .withData(categoryData)
+          .withCategoryFactory(categoryFactorySpy)
+          .parseCategory();
         // assert
-        expect(actual).to.deep.equal(expected);
+        const actualParsedScripts = getInitParameters(actualCategory)?.scripts;
+        expectExists(actualParsedScripts);
+        expect(actualParsedScripts.length).to.equal(expectedScripts.length);
+        expect(actualParsedScripts).to.have.members(expectedScripts);
       });
-      it('script is created with right context', () => { // test through script validation logic
+      it('parses all scripts with correct context', () => {
         // arrange
-        const commentDelimiter = 'should not throw';
-        const duplicatedCode = `${commentDelimiter} duplicate-line\n${commentDelimiter} duplicate-line`;
-        const parseContext = new CategoryCollectionParseContextStub()
-          .withSyntax(new LanguageSyntaxStub().withCommentDelimiters(commentDelimiter));
-        const category = new CategoryDataStub()
-          .withChildren([
-            new CategoryDataStub()
-              .withName('sub-category')
-              .withChildren([
-                createScriptDataWithoutCallOrCodes()
-                  .withCode(duplicatedCode),
-              ]),
-          ]);
+        const expectedParseContext = new CategoryCollectionParseContextStub();
+        const scriptParser = new ScriptParserStub();
+        const childrenData = [
+          createScriptDataWithCode(),
+          createScriptDataWithCode(),
+          createScriptDataWithCode(),
+        ];
+        const categoryData = new CategoryDataStub()
+          .withChildren(childrenData);
+        const { categoryFactorySpy, getInitParameters } = createCategoryFactorySpy();
         // act
-        const act = () => new TestBuilder()
-          .withData(category)
-          .withContext(parseContext)
-          .parseCategory()
-          .scripts;
+        const actualCategory = new TestBuilder()
+          .withData(categoryData)
+          .withContext(expectedParseContext)
+          .withScriptParser(scriptParser.get())
+          .withCategoryFactory(categoryFactorySpy)
+          .parseCategory();
         // assert
-        expect(act).to.not.throw();
+        const actualParsedScripts = getInitParameters(actualCategory)?.scripts;
+        expectExists(actualParsedScripts);
+        const actualParseContexts = actualParsedScripts.map(
+          (s) => scriptParser.getParseParameters(s)[1],
+        );
+        expect(
+          actualParseContexts.every(
+            (actualParseContext) => actualParseContext === expectedParseContext,
+          ),
+          formatAssertionMessage([
+            `Expected all elements to be ${JSON.stringify(expectedParseContext)}`,
+            'All elements:',
+            indentText(JSON.stringify(actualParseContexts)),
+          ]),
+        ).to.equal(true);
       });
     });
     it('returns expected subcategories', () => {
       // arrange
-      const expected = [new CategoryDataStub()
-        .withName('test category')
-        .withChildren([createScriptDataWithCode()]),
-      ];
-      const category = new CategoryDataStub()
+      const expectedChildCategory = new CategoryStub(33);
+      const childCategoryData = new CategoryDataStub()
+        .withName('expected child category')
+        .withChildren([createScriptDataWithCode()]);
+      const categoryData = new CategoryDataStub()
         .withName('category name')
-        .withChildren(expected);
+        .withChildren([childCategoryData]);
+      const { categoryFactorySpy, getInitParameters } = createCategoryFactorySpy();
       // act
-      const actual = new TestBuilder()
-        .withData(category)
-        .parseCategory()
-        .subCategories;
+      const actualCategory = new TestBuilder()
+        .withData(categoryData)
+        .withCategoryFactory((parameters) => {
+          if (parameters.name === childCategoryData.category) {
+            return expectedChildCategory;
+          }
+          return categoryFactorySpy(parameters);
+        })
+        .parseCategory();
       // assert
-      expect(actual).to.have.lengthOf(1);
-      expect(actual[0].name).to.equal(expected[0].category);
-      expect(actual[0].scripts.length).to.equal(expected[0].children.length);
+      const actualSubcategories = getInitParameters(actualCategory)?.subcategories;
+      expectExists(actualSubcategories);
+      expect(actualSubcategories).to.have.lengthOf(1);
+      expect(actualSubcategories[0]).to.equal(expectedChildCategory);
     });
   });
 });
@@ -262,24 +399,62 @@ class TestBuilder {
 
   private context: ICategoryCollectionParseContext = new CategoryCollectionParseContextStub();
 
-  private factory?: CategoryFactoryType = undefined;
+  private categoryFactory: CategoryFactory = () => new CategoryStub(33);
+
+  private errorWrapper: ErrorWithContextWrapper = new ErrorWrapperStub().get();
+
+  private validatorFactory: NodeDataValidatorFactory = createNodeDataValidatorFactoryStub;
+
+  private docsParser: DocsParser = () => ['docs'];
+
+  private scriptParser: ScriptParser = new ScriptParserStub().get();
 
   public withData(data: CategoryData) {
     this.data = data;
     return this;
   }
 
-  public withContext(context: ICategoryCollectionParseContext) {
+  public withContext(context: ICategoryCollectionParseContext): this {
     this.context = context;
     return this;
   }
 
-  public withFactory(factory: CategoryFactoryType) {
-    this.factory = factory;
+  public withCategoryFactory(categoryFactory: CategoryFactory): this {
+    this.categoryFactory = categoryFactory;
+    return this;
+  }
+
+  public withValidatorFactory(validatorFactory: NodeDataValidatorFactory): this {
+    this.validatorFactory = validatorFactory;
+    return this;
+  }
+
+  public withErrorWrapper(errorWrapper: ErrorWithContextWrapper): this {
+    this.errorWrapper = errorWrapper;
+    return this;
+  }
+
+  public withScriptParser(scriptParser: ScriptParser): this {
+    this.scriptParser = scriptParser;
+    return this;
+  }
+
+  public withDocsParser(docsParser: DocsParser): this {
+    this.docsParser = docsParser;
     return this;
   }
 
   public parseCategory() {
-    return parseCategory(this.data, this.context, this.factory);
+    return parseCategory(
+      this.data,
+      this.context,
+      {
+        createCategory: this.categoryFactory,
+        wrapError: this.errorWrapper,
+        createValidator: this.validatorFactory,
+        parseScript: this.scriptParser,
+        parseDocs: this.docsParser,
+      },
+    );
   }
 }
diff --git a/tests/unit/application/Parser/ContextualError.spec.ts b/tests/unit/application/Parser/ContextualError.spec.ts
new file mode 100644
index 000000000..b19da6519
--- /dev/null
+++ b/tests/unit/application/Parser/ContextualError.spec.ts
@@ -0,0 +1,121 @@
+import { describe, it, expect } from 'vitest';
+import { CustomError } from '@/application/Common/CustomError';
+import { wrapErrorWithAdditionalContext } from '@/application/Parser/ContextualError';
+
+describe('wrapErrorWithAdditionalContext', () => {
+  it('preserves the original error when wrapped', () => {
+    // arrange
+    const expectedError = new Error();
+    const context = new TestContext()
+      .withError(expectedError);
+    // act
+    const error = context.wrap();
+    // assert
+    const actualError = extractInnerErrorFromContextualError(error);
+    expect(actualError).to.equal(expectedError);
+  });
+  it('maintains the original error when re-wrapped', () => {
+    // arrange
+    const expectedError = new Error();
+
+    // act
+    const firstError = new TestContext()
+      .withError(expectedError)
+      .withAdditionalContext('first error')
+      .wrap();
+    const secondError = new TestContext()
+      .withError(firstError)
+      .withAdditionalContext('second error')
+      .wrap();
+
+    // assert
+    const actualError = extractInnerErrorFromContextualError(secondError);
+    expect(actualError).to.equal(expectedError);
+  });
+  it(`the object extends ${CustomError.name}`, () => {
+    // arrange
+    const expected = CustomError;
+    // act
+    const error = new TestContext()
+      .wrap();
+    // assert
+    expect(error).to.be.an.instanceof(expected);
+  });
+  describe('error message construction', () => {
+    it('includes the message from the original error', () => {
+      // arrange
+      const expectedOriginalErrorMessage = 'Message from the inner error';
+
+      // act
+      const error = new TestContext()
+        .withError(new Error(expectedOriginalErrorMessage))
+        .wrap();
+
+      // assert
+      expect(error.message).contains(expectedOriginalErrorMessage);
+    });
+    it('appends provided additional context to the error message', () => {
+      // arrange
+      const expectedAdditionalContext = 'Expected additional context message';
+
+      // act
+      const error = new TestContext()
+        .withAdditionalContext(expectedAdditionalContext)
+        .wrap();
+
+      // assert
+      expect(error.message).contains(expectedAdditionalContext);
+    });
+    it('appends multiple contexts to the error message in sequential order', () => {
+      // arrange
+      const expectedFirstContext = 'First context';
+      const expectedSecondContext = 'Second context';
+
+      // act
+      const firstError = new TestContext()
+        .withAdditionalContext(expectedFirstContext)
+        .wrap();
+      const secondError = new TestContext()
+        .withError(firstError)
+        .withAdditionalContext(expectedSecondContext)
+        .wrap();
+
+      // assert
+      const messageLines = secondError.message.split('\n');
+      expect(messageLines).to.contain(`1: ${expectedFirstContext}`);
+      expect(messageLines).to.contain(`2: ${expectedSecondContext}`);
+    });
+  });
+});
+
+class TestContext {
+  private error: Error = new Error();
+
+  private additionalContext = `[${TestContext.name}] additional context`;
+
+  public withError(error: Error) {
+    this.error = error;
+    return this;
+  }
+
+  public withAdditionalContext(additionalContext: string) {
+    this.additionalContext = additionalContext;
+    return this;
+  }
+
+  public wrap(): ReturnType<typeof wrapErrorWithAdditionalContext> {
+    return wrapErrorWithAdditionalContext(
+      this.error,
+      this.additionalContext,
+    );
+  }
+}
+
+function extractInnerErrorFromContextualError(error: Error): Error {
+  const innerErrorProperty = 'innerError';
+  if (!(innerErrorProperty in error)) {
+    throw new Error(`${innerErrorProperty} property is missing`);
+  }
+  const actualError = error[innerErrorProperty];
+  return actualError as Error;
+}
diff --git a/tests/unit/application/Parser/ContextualErrorTester.ts b/tests/unit/application/Parser/ContextualErrorTester.ts
new file mode 100644
index 000000000..1c502c57b
--- /dev/null
+++ b/tests/unit/application/Parser/ContextualErrorTester.ts
@@ -0,0 +1,53 @@
+import type { ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
+import { expectExists } from '@tests/shared/Assertions/ExpectExists';
+import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
+import { indentText } from '@tests/shared/Text';
+import { ErrorWrapperStub } from '@tests/unit/shared/Stubs/ErrorWrapperStub';
+
+interface ContextualErrorTestScenario {
+  readonly throwingAction: (wrapError: ErrorWithContextWrapper) => void;
+  readonly expectedWrappedError: Error;
+  readonly expectedContextMessage: string;
+}
+
+export function itThrowsContextualError(
+  testScenario: ContextualErrorTestScenario,
+) {
+  it('throws wrapped error', () => {
+    // arrange
+    const expectedError = new Error();
+    const wrapperStub = new ErrorWrapperStub()
+      .withError(expectedError);
+    // act
+    const act = () => testScenario.throwingAction(wrapperStub.get());
+    // assert
+    expect(act).to.throw(expectedError);
+  });
+  it('wraps internal error', () => {
+    // arrange
+    const expectedInternalError = testScenario.expectedWrappedError;
+    const wrapperStub = new ErrorWrapperStub();
+    // act
+    try {
+      testScenario.throwingAction(wrapperStub.get());
+    } catch { /* Swallow */ }
+    // assert
+    expect(wrapperStub.lastError).to.deep.equal(expectedInternalError);
+  });
+  it('includes expected context', () => {
+    // arrange
+    const { expectedContextMessage: expectedContext } = testScenario;
+    const wrapperStub = new ErrorWrapperStub();
+    // act
+    try {
+      testScenario.throwingAction(wrapperStub.get());
+    } catch { /* Swallow */ }
+    // assert
+    expectExists(wrapperStub.lastContext);
+    expect(wrapperStub.lastContext).to.equal(expectedContext, formatAssertionMessage([
+      'Unexpected additional context (additional message added to the wrapped error).',
+      `Actual additional context:\n${indentText(wrapperStub.lastContext)}`,
+      `Expected additional context:\n${indentText(expectedContext)}`,
+    ]));
+  });
+}
diff --git a/tests/unit/application/Parser/DataValidationTestScenarioGenerator.ts b/tests/unit/application/Parser/DataValidationTestScenarioGenerator.ts
new file mode 100644
index 000000000..e8d193846
--- /dev/null
+++ b/tests/unit/application/Parser/DataValidationTestScenarioGenerator.ts
@@ -0,0 +1,36 @@
+export interface DataValidationTestScenario<T> {
+  readonly description: string;
+  readonly data: T;
+  readonly expectedPass: boolean;
+  readonly expectedMessage?: string;
+}
+
+export function generateDataValidationTestScenarios<T>(
+  ...conditionBasedScenarios: DataValidationConditionBasedTestScenario<T>[]
+): DataValidationTestScenario<T>[] {
+  return conditionBasedScenarios.flatMap((conditionScenario) => [
+    conditionScenario.expectFail.map((failDefinition): DataValidationTestScenario<T> => ({
+      description: `fails: "${failDefinition.description}"`,
+      data: failDefinition.data,
+      expectedPass: false,
+      expectedMessage: conditionScenario.assertErrorMessage,
+    })),
+    conditionScenario.expectPass.map((passDefinition): DataValidationTestScenario<T> => ({
+      description: `passes: "${passDefinition.description}"`,
+      data: passDefinition.data,
+      expectedPass: true,
+      expectedMessage: conditionScenario.assertErrorMessage,
+    })),
+  ].flat());
+}
+
+interface DataValidationConditionBasedTestScenario<T> {
+  readonly assertErrorMessage?: string;
+  readonly expectPass: readonly DataValidationScenarioDefinition<T>[];
+  readonly expectFail: readonly DataValidationScenarioDefinition<T>[];
+}
+
+interface DataValidationScenarioDefinition<T> {
+  readonly description: string;
+  readonly data: T;
+}
diff --git a/tests/unit/application/Parser/NodeDataValidationTester.ts b/tests/unit/application/Parser/NodeDataValidationTester.ts
new file mode 100644
index 000000000..f6836b981
--- /dev/null
+++ b/tests/unit/application/Parser/NodeDataValidationTester.ts
@@ -0,0 +1,213 @@
+import { it } from 'vitest';
+import type { NodeDataValidator, NodeDataValidatorFactory } from '@/application/Parser/NodeValidation/NodeDataValidator';
+import type { NodeDataErrorContext } from '@/application/Parser/NodeValidation/NodeDataErrorContext';
+import { NodeDataValidatorStub } from '@tests/unit/shared/Stubs/NodeDataValidatorStub';
+import { expectExists } from '@tests/shared/Assertions/ExpectExists';
+import type { CategoryOrScriptData } from '@/application/collections/';
+import type { FunctionKeys } from '@/TypeHelpers';
+import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
+import { indentText } from '@tests/shared/Text';
+
+type NodeValidationTestFunction<TExpectation> = (
+  factory: NodeDataValidatorFactory,
+) => TExpectation;
+
+interface ValidNameExpectation {
+  readonly expectedNameToValidate: string;
+  readonly expectedErrorContext: NodeDataErrorContext;
+}
+
+export function itValidatesName(
+  test: NodeValidationTestFunction<ValidNameExpectation>,
+) {
+  it('validates for name', () => {
+    // arrange
+    const validator = new NodeDataValidatorStub();
+    const factoryStub: NodeDataValidatorFactory = () => validator;
+    // act
+    test(factoryStub);
+    // assert
+    const call = validator.callHistory.find((c) => c.methodName === 'assertValidName');
+    expectExists(call);
+  });
+  it('validates for name with correct name', () => {
+    // arrange
+    const validator = new NodeDataValidatorStub();
+    const factoryStub: NodeDataValidatorFactory = () => validator;
+    // act
+    const expectation = test(factoryStub);
+    // assert
+    const expectedName = expectation.expectedNameToValidate;
+    const names = validator.callHistory
+      .filter((c) => c.methodName === 'assertValidName')
+      .flatMap((c) => c.args[0]);
+    expect(names).to.include(expectedName);
+  });
+  it('validates for name with correct context', () => {
+    expectCorrectContextForFunctionCall({
+      methodName: 'assertValidName',
+      act: test,
+      expectContext: (expectation) => expectation.expectedErrorContext,
+    });
+  });
+}
+
+interface ValidDataExpectation {
+  readonly expectedDataToValidate: CategoryOrScriptData;
+  readonly expectedErrorContext: NodeDataErrorContext;
+}
+
+export function itValidatesDefinedData(
+  test: NodeValidationTestFunction<ValidDataExpectation>,
+) {
+  it('validates data', () => {
+    // arrange
+    const validator = new NodeDataValidatorStub();
+    const factoryStub: NodeDataValidatorFactory = () => validator;
+    // act
+    test(factoryStub);
+    // assert
+    const call = validator.callHistory.find((c) => c.methodName === 'assertDefined');
+    expectExists(call);
+  });
+  it('validates data with correct data', () => {
+    // arrange
+    const validator = new NodeDataValidatorStub();
+    const factoryStub: NodeDataValidatorFactory = () => validator;
+    // act
+    const expectation = test(factoryStub);
+    // assert
+    const expectedData = expectation.expectedDataToValidate;
+    const calls = validator.callHistory.filter((c) => c.methodName === 'assertDefined');
+    const names = calls.flatMap((c) => c.args[0]);
+    expect(names).to.include(expectedData);
+  });
+  it('validates data with correct context', () => {
+    expectCorrectContextForFunctionCall({
+      methodName: 'assertDefined',
+      act: test,
+      expectContext: (expectation) => expectation.expectedErrorContext,
+    });
+  });
+}
+
+interface AssertionExpectation {
+  readonly expectedErrorMessage: string;
+  readonly expectedErrorContext: NodeDataErrorContext;
+}
+
+export function itAsserts(
+  testScenario: {
+    readonly test: NodeValidationTestFunction<AssertionExpectation>,
+    readonly expectedConditionResult: boolean;
+  },
+) {
+  it('asserts with correct message', () => {
+    // arrange
+    const validator = new NodeDataValidatorStub()
+      .withAssertThrowsOnFalseCondition(false);
+    const factoryStub: NodeDataValidatorFactory = () => validator;
+    // act
+    const expectation = testScenario.test(factoryStub);
+    // assert
+    const expectedError = expectation.expectedErrorMessage;
+    const calls = validator.callHistory.filter((c) => c.methodName === 'assert');
+    const actualMessages = calls.map((call) => {
+      const [, message] = call.args;
+      return message;
+    });
+    expect(actualMessages).to.include(expectedError);
+  });
+  it('asserts with correct context', () => {
+    expectCorrectContextForFunctionCall({
+      methodName: 'assert',
+      act: testScenario.test,
+      expectContext: (expectation) => expectation.expectedErrorContext,
+    });
+  });
+  it('asserts with correct condition result', () => {
+    // arrange
+    const expectedEvaluationResult = testScenario.expectedConditionResult;
+    const validator = new NodeDataValidatorStub()
+      .withAssertThrowsOnFalseCondition(false);
+    const factoryStub: NodeDataValidatorFactory = () => validator;
+    // act
+    const expectation = testScenario.test(factoryStub);
+    // assert
+    const assertCalls = validator.callHistory
+      .filter((call) => call.methodName === 'assert');
+    expect(assertCalls).to.have.length.greaterThan(0);
+    const assertCallsWithMessage = assertCalls
+      .filter((call) => {
+        const [, message] = call.args;
+        return message === expectation.expectedErrorMessage;
+      });
+    expect(assertCallsWithMessage).to.have.length.greaterThan(0);
+    const evaluationResults = assertCallsWithMessage
+      .map((call) => {
+        const [predicate] = call.args;
+        return predicate as (() => boolean);
+      })
+      .map((predicate) => predicate());
+    expect(evaluationResults).to.include(expectedEvaluationResult);
+  });
+}
+
+function expectCorrectContextForFunctionCall<T>(testScenario: {
+  methodName: FunctionKeys<NodeDataValidator>,
+  act: NodeValidationTestFunction<T>,
+  expectContext: (actionResult: T) => NodeDataErrorContext,
+}) {
+  // arrange
+  const { methodName } = testScenario;
+  const createdValidators = new Array<{
+    readonly validator: NodeDataValidatorStub;
+    readonly context: NodeDataErrorContext;
+  }>();
+  const factoryStub: NodeDataValidatorFactory = (context) => {
+    const validator = new NodeDataValidatorStub()
+      .withAssertThrowsOnFalseCondition(false);
+    createdValidators.push(({
+      validator,
+      context,
+    }));
+    return validator;
+  };
+  // act
+  const actionResult = testScenario.act(factoryStub);
+  // assert
+  const expectedContext = testScenario.expectContext(actionResult);
+  const providedContexts = createdValidators
+    .filter((v) => v.validator.callHistory.find((c) => c.methodName === methodName))
+    .map((v) => v.context);
+  expectDeepIncludes( // to.deep.include is not working
+    providedContexts,
+    expectedContext,
+    formatAssertionMessage([
+      'Error context mismatch.',
+      'Provided contexts do not include the expected context.',
+      'Expected context:',
+      indentText(JSON.stringify(expectedContext, undefined, 2)),
+      'Provided contexts:',
+      indentText(JSON.stringify(providedContexts, undefined, 2)),
+    ]),
+  );
+}
+
+function expectDeepIncludes<T>(
+  array: readonly T[],
+  item: T,
+  message: string,
+) {
+  const serializeItem = (c) => JSON.stringify(c);
+  const serializedContexts = array.map((c) => serializeItem(c));
+  const serializedExpectedContext = serializeItem(item);
+  expect(serializedContexts).to.include(serializedExpectedContext, formatAssertionMessage([
+    'Error context mismatch.',
+    'Provided contexts do not include the expected context.',
+    'Expected context:',
+    indentText(JSON.stringify(message, undefined, 2)),
+    'Provided contexts:',
+    indentText(JSON.stringify(message, undefined, 2)),
+  ]));
+}
diff --git a/tests/unit/application/Parser/NodeValidation/NodeDataError.spec.ts b/tests/unit/application/Parser/NodeValidation/NodeDataError.spec.ts
deleted file mode 100644
index befb5e475..000000000
--- a/tests/unit/application/Parser/NodeValidation/NodeDataError.spec.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { type INodeDataErrorContext, NodeDataError } from '@/application/Parser/NodeValidation/NodeDataError';
-import { NodeDataErrorContextStub } from '@tests/unit/shared/Stubs/NodeDataErrorContextStub';
-import { NodeType } from '@/application/Parser/NodeValidation/NodeType';
-import { CustomError } from '@/application/Common/CustomError';
-
-describe('NodeDataError', () => {
-  it('sets message as expected', () => {
-    // arrange
-    const message = 'message';
-    const context = new NodeDataErrorContextStub();
-    const expected = `[${NodeType[context.type]}] ${message}`;
-    // act
-    const sut = new NodeDataErrorBuilder()
-      .withContext(context)
-      .withMessage(expected)
-      .build();
-    // assert
-    expect(sut.message).to.include(expected);
-  });
-  it('sets context as expected', () => {
-    // arrange
-    const expected = new NodeDataErrorContextStub();
-    // act
-    const sut = new NodeDataErrorBuilder()
-      .withContext(expected)
-      .build();
-    // assert
-    expect(sut.context).to.equal(expected);
-  });
-  it('extends CustomError', () => {
-    // arrange
-    const expected = CustomError;
-    // act
-    const sut = new NodeDataErrorBuilder()
-      .build();
-    // assert
-    expect(sut).to.be.an.instanceof(expected);
-  });
-});
-
-class NodeDataErrorBuilder {
-  private message = 'error';
-
-  private context: INodeDataErrorContext = new NodeDataErrorContextStub();
-
-  public withContext(context: INodeDataErrorContext) {
-    this.context = context;
-    return this;
-  }
-
-  public withMessage(message: string) {
-    this.message = message;
-    return this;
-  }
-
-  public build(): NodeDataError {
-    return new NodeDataError(this.message, this.context);
-  }
-}
diff --git a/tests/unit/application/Parser/NodeValidation/NodeDataValidator.spec.ts b/tests/unit/application/Parser/NodeValidation/NodeDataValidator.spec.ts
new file mode 100644
index 000000000..29357e75f
--- /dev/null
+++ b/tests/unit/application/Parser/NodeValidation/NodeDataValidator.spec.ts
@@ -0,0 +1,242 @@
+import { describe, it, expect } from 'vitest';
+import { CategoryDataStub } from '@tests/unit/shared/Stubs/CategoryDataStub';
+import type { NodeData } from '@/application/Parser/NodeValidation/NodeData';
+import { createNodeDataErrorContextStub } from '@tests/unit/shared/Stubs/NodeDataErrorContextStub';
+import type { NodeDataErrorContext } from '@/application/Parser/NodeValidation/NodeDataErrorContext';
+import { collectExceptionMessage } from '@tests/unit/shared/ExceptionCollector';
+import { ContextualNodeDataValidator, createNodeDataValidator, type NodeDataValidator } from '@/application/Parser/NodeValidation/NodeDataValidator';
+import type { NodeContextErrorMessageCreator } from '@/application/Parser/NodeValidation/NodeDataErrorContextMessage';
+import { getAbsentObjectTestCases, getAbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
+
+describe('createNodeDataValidator', () => {
+  it(`returns an instance of ${ContextualNodeDataValidator.name}`, () => {
+    // arrange
+    const context = createNodeDataErrorContextStub();
+    // act
+    const validator = createNodeDataValidator(context);
+    // assert
+    expect(validator).to.be.instanceOf(ContextualNodeDataValidator);
+  });
+});
+
+describe('NodeDataValidator', () => {
+  describe('assertValidName', () => {
+    describe('throws when name is invalid', () => {
+      // arrange
+      const testScenarios: readonly {
+        readonly description: string;
+        readonly invalidName: unknown;
+        readonly expectedMessage: string;
+      }[] = [
+        ...getAbsentStringTestCases().map((testCase) => ({
+          description: `missing name (${testCase.valueName})`,
+          invalidName: testCase.absentValue,
+          expectedMessage: 'missing name',
+        })),
+        {
+          description: 'invalid type',
+          invalidName: 33,
+          expectedMessage: 'Name (33) is not a string but number.',
+        },
+      ];
+      testScenarios.forEach(({ description, invalidName, expectedMessage }) => {
+        describe(`given "${description}"`, () => {
+          itThrowsCorrectly({
+            // act
+            throwingAction: (sut) => {
+              sut.assertValidName(invalidName as string);
+            },
+            // assert
+            expectedMessage,
+          });
+        });
+      });
+    });
+    it('does not throw when name is valid', () => {
+      // arrange
+      const validName = 'validName';
+      const sut = new NodeValidatorBuilder()
+        .build();
+      // act
+      const act = () => sut.assertValidName(validName);
+      // assert
+      expect(act).to.not.throw();
+    });
+  });
+  describe('assertDefined', () => {
+    describe('throws when node data is missing', () => {
+      // arrange
+      const testScenarios: readonly {
+        readonly description: string;
+        readonly invalidData: unknown;
+      }[] = [
+        ...getAbsentObjectTestCases().map((testCase) => ({
+          description: `absent object (${testCase.valueName})`,
+          invalidData: testCase.absentValue,
+        })),
+        {
+          description: 'empty object',
+          invalidData: {},
+        },
+      ];
+      testScenarios.forEach(({ description, invalidData }) => {
+        describe(`given "${description}"`, () => {
+          const expectedMessage = 'missing node data';
+          itThrowsCorrectly({
+            // act
+            throwingAction: (sut: NodeDataValidator) => {
+              sut.assertDefined(invalidData as NodeData);
+            },
+            // assert
+            expectedMessage,
+          });
+        });
+      });
+    });
+    it('does not throw if node data is defined', () => {
+      // arrange
+      const definedNode = new CategoryDataStub();
+      const sut = new NodeValidatorBuilder()
+        .build();
+      // act
+      const act = () => sut.assertDefined(definedNode);
+      // assert
+      expect(act).to.not.throw();
+    });
+  });
+  describe('assert', () => {
+    describe('throws if validation fails', () => {
+      const falsePredicate = () => false;
+      const expectedErrorMessage = 'expected error';
+      // assert
+      itThrowsCorrectly({
+        // act
+        throwingAction: (sut: NodeDataValidator) => {
+          sut.assert(falsePredicate, expectedErrorMessage);
+        },
+        // assert
+        expectedMessage: expectedErrorMessage,
+      });
+    });
+    it('does not throw if validation succeeds', () => {
+      // arrange
+      const truePredicate = () => true;
+      const sut = new NodeValidatorBuilder()
+        .build();
+      // act
+      const act = () => sut.assert(truePredicate, 'ignored error');
+      // assert
+      expect(act).to.not.throw();
+    });
+  });
+  describe('createContextualErrorMessage', () => {
+    it('creates using the correct error message', () => {
+      // arrange
+      const expectedErrorMessage = 'expected error';
+      const errorMessageBuilder: NodeContextErrorMessageCreator = (message) => message;
+      const sut = new NodeValidatorBuilder()
+        .withErrorMessageCreator(errorMessageBuilder)
+        .build();
+      // act
+      const actualErrorMessage = sut.createContextualErrorMessage(expectedErrorMessage);
+      // assert
+      expect(actualErrorMessage).to.equal(expectedErrorMessage);
+    });
+    it('creates using the correct context', () => {
+      // arrange
+      const expectedContext = createNodeDataErrorContextStub();
+      let actualContext: NodeDataErrorContext | undefined;
+      const errorMessageBuilder: NodeContextErrorMessageCreator = (_, context) => {
+        actualContext = context;
+        return '';
+      };
+      const sut = new NodeValidatorBuilder()
+        .withContext(expectedContext)
+        .withErrorMessageCreator(errorMessageBuilder)
+        .build();
+      // act
+      sut.createContextualErrorMessage('unimportant');
+      // assert
+      expect(actualContext).to.equal(expectedContext);
+    });
+  });
+});
+
+type ValidationThrowingFunction = (
+  sut: ContextualNodeDataValidator,
+) => void;
+
+interface ValidationThrowingTestScenario {
+  readonly throwingAction: ValidationThrowingFunction,
+  readonly expectedMessage: string;
+}
+
+function itThrowsCorrectly(
+  testScenario: ValidationThrowingTestScenario,
+): void {
+  it('throws an error', () => {
+    // arrange
+    const expectedErrorMessage = 'Injected error message';
+    const errorMessageBuilder: NodeContextErrorMessageCreator = () => expectedErrorMessage;
+    const sut = new NodeValidatorBuilder()
+      .withErrorMessageCreator(errorMessageBuilder)
+      .build();
+    // act
+    const action = () => testScenario.throwingAction(sut);
+    // assert
+    expect(action).to.throw();
+  });
+  it('throws with the correct error message', () => {
+    // arrange
+    const expectedErrorMessage = testScenario.expectedMessage;
+    const errorMessageBuilder: NodeContextErrorMessageCreator = (message) => message;
+    const sut = new NodeValidatorBuilder()
+      .withErrorMessageCreator(errorMessageBuilder)
+      .build();
+    // act
+    const action = () => testScenario.throwingAction(sut);
+    // assert
+    const actualErrorMessage = collectExceptionMessage(action);
+    expect(actualErrorMessage).to.equal(expectedErrorMessage);
+  });
+  it('throws with the correct context', () => {
+    // arrange
+    const expectedContext = createNodeDataErrorContextStub();
+    const serializeContext = (context: NodeDataErrorContext) => JSON.stringify(context);
+    const errorMessageBuilder:
+    NodeContextErrorMessageCreator = (_, context) => serializeContext(context);
+    const sut = new NodeValidatorBuilder()
+      .withContext(expectedContext)
+      .withErrorMessageCreator(errorMessageBuilder)
+      .build();
+    // act
+    const action = () => testScenario.throwingAction(sut);
+    // assert
+    const expectedSerializedContext = serializeContext(expectedContext);
+    const actualSerializedContext = collectExceptionMessage(action);
+    expect(expectedSerializedContext).to.equal(actualSerializedContext);
+  });
+}
+
+class NodeValidatorBuilder {
+  private errorContext: NodeDataErrorContext = createNodeDataErrorContextStub();
+
+  private errorMessageCreator: NodeContextErrorMessageCreator = () => `[${NodeValidatorBuilder.name}] stub error message`;
+
+  public withErrorMessageCreator(errorMessageCreator: NodeContextErrorMessageCreator): this {
+    this.errorMessageCreator = errorMessageCreator;
+    return this;
+  }
+
+  public withContext(errorContext: NodeDataErrorContext): this {
+    this.errorContext = errorContext;
+    return this;
+  }
+
+  public build(): ContextualNodeDataValidator {
+    return new ContextualNodeDataValidator(
+      this.errorContext,
+      this.errorMessageCreator,
+    );
+  }
+}
diff --git a/tests/unit/application/Parser/NodeValidation/NodeValidator.spec.ts b/tests/unit/application/Parser/NodeValidation/NodeValidator.spec.ts
deleted file mode 100644
index ab9a989c7..000000000
--- a/tests/unit/application/Parser/NodeValidation/NodeValidator.spec.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { NodeDataError } from '@/application/Parser/NodeValidation/NodeDataError';
-import { NodeValidator } from '@/application/Parser/NodeValidation/NodeValidator';
-import { expectDeepThrowsError } from '@tests/shared/Assertions/ExpectDeepThrowsError';
-import { CategoryDataStub } from '@tests/unit/shared/Stubs/CategoryDataStub';
-import { NodeDataErrorContextStub } from '@tests/unit/shared/Stubs/NodeDataErrorContextStub';
-import type { NodeData } from '@/application/Parser/NodeValidation/NodeData';
-import { NodeValidationTestRunner } from './NodeValidatorTestRunner';
-
-describe('NodeValidator', () => {
-  describe('assertValidName', () => {
-    describe('throws if invalid', () => {
-      // arrange
-      const context = new NodeDataErrorContextStub();
-      const sut = new NodeValidator(context);
-      // act
-      const act = (invalidName: string) => sut.assertValidName(invalidName);
-      // assert
-      new NodeValidationTestRunner()
-        .testInvalidNodeName((invalidName) => ({
-          act: () => act(invalidName),
-          expectedContext: context,
-        }));
-    });
-    it('does not throw if valid', () => {
-      // arrange
-      const validName = 'validName';
-      const sut = new NodeValidator(new NodeDataErrorContextStub());
-      // act
-      const act = () => sut.assertValidName(validName);
-      // assert
-      expect(act).to.not.throw();
-    });
-  });
-  describe('assertDefined', () => {
-    describe('throws if missing', () => {
-      // arrange
-      const context = new NodeDataErrorContextStub();
-      const sut = new NodeValidator(context);
-      // act
-      const act = (undefinedNode: NodeData) => sut.assertDefined(undefinedNode);
-      // assert
-      new NodeValidationTestRunner()
-        .testMissingNodeData((invalidName) => ({
-          act: () => act(invalidName),
-          expectedContext: context,
-        }));
-    });
-    it('does not throw if defined', () => {
-      // arrange
-      const definedNode = mockNode();
-      const sut = new NodeValidator(new NodeDataErrorContextStub());
-      // act
-      const act = () => sut.assertDefined(definedNode);
-      // assert
-      expect(act).to.not.throw();
-    });
-  });
-  describe('assert', () => {
-    it('throws expected error if condition is false', () => {
-      // arrange
-      const message = 'error';
-      const falsePredicate = () => false;
-      const context = new NodeDataErrorContextStub();
-      const expected = new NodeDataError(message, context);
-      const sut = new NodeValidator(context);
-      // act
-      const act = () => sut.assert(falsePredicate, message);
-      // assert
-      expectDeepThrowsError(act, expected);
-    });
-    it('does not throw if condition is true', () => {
-      // arrange
-      const truePredicate = () => true;
-      const sut = new NodeValidator(new NodeDataErrorContextStub());
-      // act
-      const act = () => sut.assert(truePredicate, 'ignored error');
-      // assert
-      expect(act).to.not.throw();
-    });
-  });
-  describe('throw', () => {
-    it('throws expected error', () => {
-      // arrange
-      const message = 'error';
-      const context = new NodeDataErrorContextStub();
-      const expected = new NodeDataError(message, context);
-      const sut = new NodeValidator(context);
-      // act
-      const act = () => sut.throw(message);
-      // assert
-      expectDeepThrowsError(act, expected);
-    });
-  });
-});
-
-function mockNode() {
-  return new CategoryDataStub();
-}
diff --git a/tests/unit/application/Parser/NodeValidation/NodeValidatorTestRunner.ts b/tests/unit/application/Parser/NodeValidation/NodeValidatorTestRunner.ts
deleted file mode 100644
index 29d144721..000000000
--- a/tests/unit/application/Parser/NodeValidation/NodeValidatorTestRunner.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import { describe, it } from 'vitest';
-import { NodeDataError, type INodeDataErrorContext } from '@/application/Parser/NodeValidation/NodeDataError';
-import type { NodeData } from '@/application/Parser/NodeValidation/NodeData';
-import { getAbsentObjectTestCases, getAbsentStringTestCases, itEachAbsentTestCase } from '@tests/unit/shared/TestCases/AbsentTests';
-import { expectDeepThrowsError } from '@tests/shared/Assertions/ExpectDeepThrowsError';
-
-export interface ITestScenario {
-  readonly act: () => void;
-  readonly expectedContext: INodeDataErrorContext;
-}
-
-export class NodeValidationTestRunner {
-  public testInvalidNodeName(
-    testBuildPredicate: (invalidName: string) => ITestScenario,
-  ) {
-    describe('throws given invalid names', () => {
-      // arrange
-      const testCases = [
-        ...getAbsentStringTestCases().map((testCase) => ({
-          testName: `missing name (${testCase.valueName})`,
-          nameValue: testCase.absentValue,
-          expectedMessage: 'missing name',
-        })),
-        {
-          testName: 'invalid type',
-          nameValue: 33,
-          expectedMessage: 'Name (33) is not a string but number.',
-        },
-      ];
-      for (const testCase of testCases) {
-        it(`given "${testCase.testName}"`, () => {
-          const test = testBuildPredicate(testCase.nameValue as never);
-          expectThrowsNodeError(test, testCase.expectedMessage);
-        });
-      }
-    });
-    return this;
-  }
-
-  public testMissingNodeData(
-    testBuildPredicate: (missingNode: NodeData) => ITestScenario,
-  ) {
-    describe('throws given missing node data', () => {
-      itEachAbsentTestCase([
-        ...getAbsentObjectTestCases(),
-        {
-          valueName: 'empty object',
-          absentValue: {},
-        },
-      ], (absentValue) => {
-        // arrange
-        const expectedError = 'missing node data';
-        // act
-        const test = testBuildPredicate(absentValue as NodeData);
-        // assert
-        expectThrowsNodeError(test, expectedError);
-      });
-    });
-    return this;
-  }
-
-  public runThrowingCase(
-    testCase: {
-      readonly name: string,
-      readonly scenario: ITestScenario,
-      readonly expectedMessage: string
-    },
-  ) {
-    it(testCase.name, () => {
-      expectThrowsNodeError(testCase.scenario, testCase.expectedMessage);
-    });
-    return this;
-  }
-}
-
-export function expectThrowsNodeError(
-  test: ITestScenario,
-  expectedMessage: string,
-) {
-  // arrange
-  const expected = new NodeDataError(expectedMessage, test.expectedContext);
-  // act
-  const act = () => test.act();
-  // assert
-  expectDeepThrowsError(act, expected);
-  return this;
-}
diff --git a/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/Expression.spec.ts b/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/Expression.spec.ts
index e62641ded..31203f347 100644
--- a/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/Expression.spec.ts
+++ b/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/Expression.spec.ts
@@ -229,7 +229,11 @@ class ExpressionBuilder {
   }
 
   public build() {
-    return new Expression(this.position, this.evaluator, this.parameters);
+    return new Expression({
+      position: this.position,
+      evaluator: this.evaluator,
+      parameters: this.parameters,
+    });
   }
 
   private evaluator: ExpressionEvaluator = () => `[${ExpressionBuilder.name}] evaluated-expression`;
diff --git a/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/ExpressiontPositionFactory.spec.ts b/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPositionFactory.spec.ts
similarity index 84%
rename from tests/unit/application/Parser/Script/Compiler/Expressions/Expression/ExpressiontPositionFactory.spec.ts
rename to tests/unit/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPositionFactory.spec.ts
index 038e6efef..ce7e66e51 100644
--- a/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/ExpressiontPositionFactory.spec.ts
+++ b/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPositionFactory.spec.ts
@@ -1,22 +1,21 @@
 import { describe, it, expect } from 'vitest';
 import { createPositionFromRegexFullMatch } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPositionFactory';
 import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
+import { itIsTransientFactory } from '@tests/unit/shared/TestCases/TransientFactoryTests';
 
 describe('ExpressionPositionFactory', () => {
   describe('createPositionFromRegexFullMatch', () => {
-    it(`creates ${ExpressionPosition.name} instance`, () => {
+    describe('it is a transient factory', () => {
       // arrange
-      const expectedType = ExpressionPosition;
-      const fakeMatch = createRegexMatch({
-        fullMatch: 'matched string',
-        matchIndex: 5,
-      });
+      const fakeMatch = createRegexMatch();
       // act
-      const position = createPositionFromRegexFullMatch(fakeMatch);
+      const create = () => createPositionFromRegexFullMatch(fakeMatch);
       // assert
-      expect(position).to.be.instanceOf(expectedType);
+      itIsTransientFactory({
+        getter: create,
+        expectedType: ExpressionPosition,
+      });
     });
-
     it('creates a position with the correct start position', () => {
       // arrange
       const expectedStartPosition = 5;
@@ -63,10 +62,8 @@ describe('ExpressionPositionFactory', () => {
     describe('invalid values', () => {
       it('throws an error if match.index is undefined', () => {
         // arrange
-        const fakeMatch = createRegexMatch({
-          fullMatch: 'matched string',
-          matchIndex: undefined,
-        });
+        const fakeMatch = createRegexMatch();
+        fakeMatch.index = undefined;
         const expectedError = `Regex match did not yield any results: ${JSON.stringify(fakeMatch)}`;
         // act
         const act = () => createPositionFromRegexFullMatch(fakeMatch);
@@ -94,9 +91,9 @@ function createRegexMatch(options?: {
   readonly capturingGroups?: readonly string[],
   readonly matchIndex?: number,
 }): RegExpMatchArray {
-  const fullMatch = options?.fullMatch ?? 'fake match';
+  const fullMatch = options?.fullMatch ?? 'default fake match';
   const capturingGroups = options?.capturingGroups ?? [];
   const fakeMatch: RegExpMatchArray = [fullMatch, ...capturingGroups];
-  fakeMatch.index = options?.matchIndex;
+  fakeMatch.index = options?.matchIndex ?? 0;
   return fakeMatch;
 }
diff --git a/tests/unit/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.spec.ts b/tests/unit/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.spec.ts
index a8163a193..807ce2735 100644
--- a/tests/unit/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.spec.ts
+++ b/tests/unit/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.spec.ts
@@ -1,168 +1,438 @@
 import { describe, it, expect } from 'vitest';
-import type { ExpressionEvaluator } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
-import { type IPrimitiveExpression, RegexParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser';
+import type {
+  ExpressionEvaluator, ExpressionInitParameters,
+} from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
+import {
+  type PrimitiveExpression, RegexParser, type ExpressionFactory, type RegexParserUtilities,
+} from '@/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser';
 import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
-import { FunctionParameterStub } from '@tests/unit/shared/Stubs/FunctionParameterStub';
 import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
-import { expectExists } from '@tests/shared/Assertions/ExpectExists';
+import { collectExceptionMessage } from '@tests/unit/shared/ExceptionCollector';
+import { itThrowsContextualError } from '@tests/unit/application/Parser/ContextualErrorTester';
+import { ExpressionStub } from '@tests/unit/shared/Stubs/ExpressionStub';
+import { FunctionParameterCollectionStub } from '@tests/unit/shared/Stubs/FunctionParameterCollectionStub';
+import type { IExpression } from '@/application/Parser/Script/Compiler/Expressions/Expression/IExpression';
+import { FunctionParameterStub } from '@tests/unit/shared/Stubs/FunctionParameterStub';
+import type { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
+import type { ExpressionPositionFactory } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPositionFactory';
+import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
+import { indentText } from '@tests/shared/Text';
+import type { FunctionParameterCollectionFactory } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollectionFactory';
 
 describe('RegexParser', () => {
   describe('findExpressions', () => {
-    describe('throws when code is absent', () => {
-      itEachAbsentStringValue((absentValue) => {
+    describe('error handling', () => {
+      describe('throws when code is absent', () => {
+        itEachAbsentStringValue((absentValue) => {
+          // arrange
+          const expectedError = 'missing code';
+          const sut = new RegexParserConcrete({
+            regex: /unimportant/,
+          });
+          // act
+          const act = () => sut.findExpressions(absentValue);
+          // assert
+          const errorMessage = collectExceptionMessage(act);
+          expect(errorMessage).to.include(expectedError);
+        }, { excludeNull: true, excludeUndefined: true });
+      });
+      describe('rethrows regex match errors', () => {
         // arrange
-        const expectedError = 'missing code';
-        const sut = new RegexParserConcrete(/unimportant/);
-        // act
-        const act = () => sut.findExpressions(absentValue);
-        // assert
-        expect(act).to.throw(expectedError);
-      }, { excludeNull: true, excludeUndefined: true });
-    });
-    it('throws when position is invalid', () => {
-      // arrange
-      const regexMatchingEmpty = /^/gm; /* expressions cannot be empty */
-      const code = 'unimportant';
-      const expectedErrorParts = [
-        `[${RegexParserConcrete.constructor.name}]`,
-        'invalid script position',
-        `Regex: ${regexMatchingEmpty}`,
-        `Code: ${code}`,
-      ];
-      const sut = new RegexParserConcrete(regexMatchingEmpty);
-      // act
-      let errorMessage: string | undefined;
-      try {
-        sut.findExpressions(code);
-      } catch (err) {
-        errorMessage = err.message;
-      }
-      // assert
-      expectExists(errorMessage);
-      const error = errorMessage; // workaround for ts(18048): possibly 'undefined'
-      expect(
-        expectedErrorParts.every((part) => error.includes(part)),
-        `Expected parts: ${expectedErrorParts.join(', ')}`
-          + `Actual error: ${errorMessage}`,
-      );
+        const expectedMatchError = new TypeError('String.prototype.matchAll called with a non-global RegExp argument');
+        const expectedMessage = 'Failed to match regex.';
+        const expectedCodeInMessage = 'unimportant code content';
+        const expectedRegexInMessage = /failing-regex-because-it-is-non-global/;
+        const expectedErrorMessage = buildRethrowErrorMessage({
+          message: expectedMessage,
+          code: expectedCodeInMessage,
+          regex: expectedRegexInMessage,
+        });
+        itThrowsContextualError({
+          // act
+          throwingAction: (wrapError) => {
+            const sut = new RegexParserConcrete(
+              {
+                regex: expectedRegexInMessage,
+                utilities: {
+                  wrapError,
+                },
+              },
+            );
+            sut.findExpressions(expectedCodeInMessage);
+          },
+          // assert
+          expectedContextMessage: expectedErrorMessage,
+          expectedWrappedError: expectedMatchError,
+        });
+      });
+      describe('rethrows expression building errors', () => {
+        // arrange
+        const expectedMessage = 'Failed to build expression.';
+        const expectedInnerError = new Error('Expected error from building expression');
+        const {
+          code: expectedCodeInMessage,
+          regex: expectedRegexInMessage,
+        } = createCodeAndRegexMatchingOnce();
+        const throwingExpressionBuilder = () => {
+          throw expectedInnerError;
+        };
+        const expectedErrorMessage = buildRethrowErrorMessage({
+          message: expectedMessage,
+          code: expectedCodeInMessage,
+          regex: expectedRegexInMessage,
+        });
+        itThrowsContextualError({
+          // act
+          throwingAction: (wrapError) => {
+            const sut = new RegexParserConcrete(
+              {
+                regex: expectedRegexInMessage,
+                builder: throwingExpressionBuilder,
+                utilities: {
+                  wrapError,
+                },
+              },
+            );
+            sut.findExpressions(expectedCodeInMessage);
+          },
+          // assert
+          expectedWrappedError: expectedInnerError,
+          expectedContextMessage: expectedErrorMessage,
+        });
+      });
+      describe('rethrows position creation errors', () => {
+        // arrange
+        const expectedMessage = 'Failed to create position.';
+        const expectedInnerError = new Error('Expected error from position factory');
+        const {
+          code: expectedCodeInMessage,
+          regex: expectedRegexInMessage,
+        } = createCodeAndRegexMatchingOnce();
+        const throwingPositionFactory = () => {
+          throw expectedInnerError;
+        };
+        const expectedErrorMessage = buildRethrowErrorMessage({
+          message: expectedMessage,
+          code: expectedCodeInMessage,
+          regex: expectedRegexInMessage,
+        });
+        itThrowsContextualError({
+          // act
+          throwingAction: (wrapError) => {
+            const sut = new RegexParserConcrete(
+              {
+                regex: expectedRegexInMessage,
+                utilities: {
+                  createPosition: throwingPositionFactory,
+                  wrapError,
+                },
+              },
+            );
+            sut.findExpressions(expectedCodeInMessage);
+          },
+          // assert
+          expectedWrappedError: expectedInnerError,
+          expectedContextMessage: expectedErrorMessage,
+        });
+      });
+      describe('rethrows parameter creation errors', () => {
+        // arrange
+        const expectedMessage = 'Failed to create parameters.';
+        const expectedInnerError = new Error('Expected error from parameter collection factory');
+        const {
+          code: expectedCodeInMessage,
+          regex: expectedRegexInMessage,
+        } = createCodeAndRegexMatchingOnce();
+        const throwingParameterCollectionFactory = () => {
+          throw expectedInnerError;
+        };
+        const expectedErrorMessage = buildRethrowErrorMessage({
+          message: expectedMessage,
+          code: expectedCodeInMessage,
+          regex: expectedRegexInMessage,
+        });
+        itThrowsContextualError({
+          // act
+          throwingAction: (wrapError) => {
+            const sut = new RegexParserConcrete(
+              {
+                regex: expectedRegexInMessage,
+                utilities: {
+                  createParameterCollection: throwingParameterCollectionFactory,
+                  wrapError,
+                },
+              },
+            );
+            sut.findExpressions(expectedCodeInMessage);
+          },
+          // assert
+          expectedWrappedError: expectedInnerError,
+          expectedContextMessage: expectedErrorMessage,
+        });
+      });
+      describe('rethrows expression creation errors', () => {
+        // arrange
+        const expectedMessage = 'Failed to create expression.';
+        const expectedInnerError = new Error('Expected error from expression factory');
+        const {
+          code: expectedCodeInMessage,
+          regex: expectedRegexInMessage,
+        } = createCodeAndRegexMatchingOnce();
+        const throwingExpressionFactory = () => {
+          throw expectedInnerError;
+        };
+        const expectedErrorMessage = buildRethrowErrorMessage({
+          message: expectedMessage,
+          code: expectedCodeInMessage,
+          regex: expectedRegexInMessage,
+        });
+        itThrowsContextualError({
+          // act
+          throwingAction: (wrapError) => {
+            const sut = new RegexParserConcrete(
+              {
+                regex: expectedRegexInMessage,
+                utilities: {
+                  createExpression: throwingExpressionFactory,
+                  wrapError,
+                },
+              },
+            );
+            sut.findExpressions(expectedCodeInMessage);
+          },
+          // assert
+          expectedWrappedError: expectedInnerError,
+          expectedContextMessage: expectedErrorMessage,
+        });
+      });
     });
-    describe('matches regex as expected', () => {
+    describe('handles matched regex correctly', () => {
       // arrange
-      const testCases = [
+      const testScenarios: readonly {
+        readonly description: string;
+        readonly regex: RegExp;
+        readonly code: string;
+      }[] = [
         {
-          name: 'returns no result when regex does not match',
+          description: 'non-matching regex',
           regex: /hello/g,
           code: 'world',
         },
         {
-          name: 'returns expected when regex matches single',
+          description: 'single regex match',
           regex: /hello/g,
           code: 'hello world',
         },
         {
-          name: 'returns expected when regex matches multiple',
+          description: 'multiple regex matches',
           regex: /l/g,
           code: 'hello world',
         },
       ];
-      for (const testCase of testCases) {
-        it(testCase.name, () => {
-          const expected = Array.from(testCase.code.matchAll(testCase.regex));
-          const matches = new Array<RegExpMatchArray>();
-          const builder = (m: RegExpMatchArray): IPrimitiveExpression => {
-            matches.push(m);
-            return mockPrimitiveExpression();
-          };
-          const sut = new RegexParserConcrete(testCase.regex, builder);
-          // act
-          const expressions = sut.findExpressions(testCase.code);
-          // assert
-          expect(expressions).to.have.lengthOf(matches.length);
-          expect(matches).to.deep.equal(expected);
+      testScenarios.forEach(({
+        description, code, regex,
+      }) => {
+        describe(description, () => {
+          it('generates expressions for all matches', () => {
+            // arrange
+            const expectedTotalExpressions = Array.from(code.matchAll(regex)).length;
+            const sut = new RegexParserConcrete({
+              regex,
+            });
+            // act
+            const expressions = sut.findExpressions(code);
+            // assert
+            const actualTotalExpressions = expressions.length;
+            expect(actualTotalExpressions).to.equal(
+              expectedTotalExpressions,
+              formatAssertionMessage([
+                `Expected ${actualTotalExpressions} expressions due to ${expectedTotalExpressions} matches`,
+                `Expressions:\n${indentText(JSON.stringify(expressions, undefined, 2))}`,
+              ]),
+            );
+          });
+          it('builds primitive expressions for each match', () => {
+            const expected = Array.from(code.matchAll(regex));
+            const matches = new Array<RegExpMatchArray>();
+            const builder = (m: RegExpMatchArray): PrimitiveExpression => {
+              matches.push(m);
+              return createPrimitiveExpressionStub();
+            };
+            const sut = new RegexParserConcrete({
+              regex,
+              builder,
+            });
+            // act
+            sut.findExpressions(code);
+            // assert
+            expect(matches).to.deep.equal(expected);
+          });
+          it('sets positions correctly from matches', () => {
+            // arrange
+            const expectedMatches = [...code.matchAll(regex)];
+            const { createExpression, getInitParameters } = createExpressionFactorySpy();
+            const serializeRegexMatch = (match: RegExpMatchArray) => `[startPos:${match?.index ?? 'none'},length:${match?.[0]?.length ?? 'none'}]`;
+            const positionsForMatches = new Map<string, ExpressionPosition>(expectedMatches.map(
+              (expectedMatch) => [serializeRegexMatch(expectedMatch), new ExpressionPosition(1, 4)],
+            ));
+            const createPositionMock: ExpressionPositionFactory = (match) => {
+              const position = positionsForMatches.get(serializeRegexMatch(match));
+              return position ?? new ExpressionPosition(66, 666);
+            };
+            const sut = new RegexParserConcrete({
+              regex,
+              utilities: {
+                createExpression,
+                createPosition: createPositionMock,
+              },
+            });
+            // act
+            const expressions = sut.findExpressions(code);
+            // assert
+            const expectedPositions = [...positionsForMatches.values()];
+            const actualPositions = expressions.map((e) => getInitParameters(e)?.position);
+            expect(actualPositions).to.deep.equal(expectedPositions, formatAssertionMessage([
+              'Actual positions do not match the expected positions.',
+              `Expected total positions: ${expectedPositions.length} (due to ${expectedMatches.length} regex matches)`,
+              `Actual total positions: ${actualPositions.length}`,
+              `Expected positions:\n${indentText(JSON.stringify(expectedPositions, undefined, 2))}`,
+              `Actual positions:\n${indentText(JSON.stringify(actualPositions, undefined, 2))}`,
+            ]));
+          });
         });
-      }
+      });
     });
-    it('sets evaluator as expected', () => {
+    it('sets evaluator correctly from expression', () => {
       // arrange
-      const expected = getEvaluatorStub();
-      const regex = /hello/g;
-      const code = 'hello';
-      const builder = (): IPrimitiveExpression => ({
-        evaluator: expected,
+      const { createExpression, getInitParameters } = createExpressionFactorySpy();
+      const expectedEvaluate = createEvaluatorStub();
+      const { code, regex } = createCodeAndRegexMatchingOnce();
+      const builder = (): PrimitiveExpression => ({
+        evaluator: expectedEvaluate,
+      });
+      const sut = new RegexParserConcrete({
+        regex,
+        builder,
+        utilities: {
+          createExpression,
+        },
       });
-      const sut = new RegexParserConcrete(regex, builder);
       // act
       const expressions = sut.findExpressions(code);
       // assert
       expect(expressions).to.have.lengthOf(1);
-      expect(expressions[0].evaluate === expected);
+      const actualEvaluate = getInitParameters(expressions[0])?.evaluator;
+      expect(actualEvaluate).to.equal(expectedEvaluate);
     });
-    it('sets parameters as expected', () => {
+    it('sets parameters correctly from expression', () => {
       // arrange
-      const expected = [
-        new FunctionParameterStub().withName('parameter1').withOptionality(true),
-        new FunctionParameterStub().withName('parameter2').withOptionality(false),
+      const expectedParameters: IReadOnlyFunctionParameterCollection['all'] = [
+        new FunctionParameterStub().withName('parameter1').withOptional(true),
+        new FunctionParameterStub().withName('parameter2').withOptional(false),
       ];
       const regex = /hello/g;
       const code = 'hello';
-      const builder = (): IPrimitiveExpression => ({
-        evaluator: getEvaluatorStub(),
-        parameters: expected,
+      const builder = (): PrimitiveExpression => ({
+        evaluator: createEvaluatorStub(),
+        parameters: expectedParameters,
+      });
+      const parameterCollection = new FunctionParameterCollectionStub();
+      const parameterCollectionFactoryStub
+      : FunctionParameterCollectionFactory = () => parameterCollection;
+      const { createExpression, getInitParameters } = createExpressionFactorySpy();
+      const sut = new RegexParserConcrete({
+        regex,
+        builder,
+        utilities: {
+          createExpression,
+          createParameterCollection: parameterCollectionFactoryStub,
+        },
       });
-      const sut = new RegexParserConcrete(regex, builder);
       // act
       const expressions = sut.findExpressions(code);
       // assert
       expect(expressions).to.have.lengthOf(1);
-      expect(expressions[0].parameters.all).to.deep.equal(expected);
-    });
-    it('sets expected position', () => {
-      // arrange
-      const code = 'mate date in state is fate';
-      const regex = /ate/g;
-      const expected = [
-        new ExpressionPosition(1, 4),
-        new ExpressionPosition(6, 9),
-        new ExpressionPosition(15, 18),
-        new ExpressionPosition(23, 26),
-      ];
-      const sut = new RegexParserConcrete(regex);
-      // act
-      const expressions = sut.findExpressions(code);
-      // assert
-      const actual = expressions.map((e) => e.position);
-      expect(actual).to.deep.equal(expected);
+      const actualParameters = getInitParameters(expressions[0])?.parameters;
+      expect(actualParameters).to.equal(parameterCollection);
+      expect(actualParameters?.all).to.deep.equal(expectedParameters);
     });
   });
 });
 
-function mockBuilder(): (match: RegExpMatchArray) => IPrimitiveExpression {
+function buildRethrowErrorMessage(
+  expectedContext: {
+    readonly message: string;
+    readonly regex: RegExp;
+    readonly code: string;
+  },
+): string {
+  return [
+    expectedContext.message,
+    `Class name: ${RegexParserConcrete.name}`,
+    `Regex pattern used: ${expectedContext.regex}`,
+    `Code: ${expectedContext.code}`,
+  ].join('\n');
+}
+
+function createExpressionFactorySpy() {
+  const createdExpressions = new Map<IExpression, ExpressionInitParameters>();
+  const createExpression: ExpressionFactory = (parameters) => {
+    const expression = new ExpressionStub();
+    createdExpressions.set(expression, parameters);
+    return expression;
+  };
+  return {
+    createExpression,
+    getInitParameters: (expression) => createdExpressions.get(expression),
+  };
+}
+
+function createBuilderStub(): (match: RegExpMatchArray) => PrimitiveExpression {
   return () => ({
-    evaluator: getEvaluatorStub(),
+    evaluator: createEvaluatorStub(),
   });
 }
-function getEvaluatorStub(): ExpressionEvaluator {
-  return () => `[${getEvaluatorStub.name}] evaluated code`;
+function createEvaluatorStub(): ExpressionEvaluator {
+  return () => `[${createEvaluatorStub.name}] evaluated code`;
 }
 
-function mockPrimitiveExpression(): IPrimitiveExpression {
+function createPrimitiveExpressionStub(): PrimitiveExpression {
   return {
-    evaluator: getEvaluatorStub(),
+    evaluator: createEvaluatorStub(),
   };
 }
 
+function createCodeAndRegexMatchingOnce() {
+  const code = 'expected code in context';
+  const regex = /code/g;
+  return { code, regex };
+}
+
 class RegexParserConcrete extends RegexParser {
+  private readonly builder: RegexParser['buildExpression'];
+
   protected regex: RegExp;
 
-  public constructor(
-    regex: RegExp,
-    private readonly builder = mockBuilder(),
-  ) {
-    super();
-    this.regex = regex;
+  public constructor(parameters?: {
+    regex?: RegExp,
+    builder?: RegexParser['buildExpression'],
+    utilities?: Partial<RegexParserUtilities>,
+  }) {
+    super({
+      wrapError: parameters?.utilities?.wrapError
+        ?? (() => new Error(`[${RegexParserConcrete}] wrapped error`)),
+      createPosition: parameters?.utilities?.createPosition
+        ?? (() => new ExpressionPosition(0, 5)),
+      createExpression: parameters?.utilities?.createExpression
+        ?? (() => new ExpressionStub()),
+      createParameterCollection: parameters?.utilities?.createParameterCollection
+        ?? (() => new FunctionParameterCollectionStub()),
+    });
+    this.builder = parameters?.builder ?? createBuilderStub();
+    this.regex = parameters?.regex ?? /unimportant/g;
   }
 
-  protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
+  protected buildExpression(match: RegExpMatchArray): PrimitiveExpression {
     return this.builder(match);
   }
 }
diff --git a/tests/unit/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.spec.ts
index 793ca4ac4..720aed9d8 100644
--- a/tests/unit/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.spec.ts
+++ b/tests/unit/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.spec.ts
@@ -17,7 +17,7 @@ describe('FunctionCallArgument', () => {
       itEachAbsentStringValue((absentValue) => {
         // arrange
         const parameterName = 'paramName';
-        const expectedError = `missing argument value for "${parameterName}"`;
+        const expectedError = `Missing argument value for the parameter "${parameterName}".`;
         const argumentValue = absentValue;
         // act
         const act = () => new FunctionCallArgumentBuilder()
diff --git a/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallSequenceCompiler.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallSequenceCompiler.spec.ts
index 54f489423..8a8b0a53c 100644
--- a/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallSequenceCompiler.spec.ts
+++ b/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallSequenceCompiler.spec.ts
@@ -1,7 +1,7 @@
 /* eslint-disable max-classes-per-file */
 import { describe, it, expect } from 'vitest';
 import { FunctionCallSequenceCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallSequenceCompiler';
-import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
+import { itIsSingletonFactory } from '@tests/unit/shared/TestCases/SingletonFactoryTests';
 import { itEachAbsentCollectionValue } from '@tests/unit/shared/TestCases/AbsentTests';
 import type { SingleCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/SingleCallCompiler';
 import type { CodeSegmentMerger } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CodeSegmentJoin/CodeSegmentMerger';
@@ -17,7 +17,7 @@ import { expectExists } from '@tests/shared/Assertions/ExpectExists';
 
 describe('FunctionCallSequenceCompiler', () => {
   describe('instance', () => {
-    itIsSingleton({
+    itIsSingletonFactory({
       getter: () => FunctionCallSequenceCompiler.instance,
       expectedType: FunctionCallSequenceCompiler,
     });
diff --git a/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/NestedFunctionCallCompiler.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/NestedFunctionCallCompiler.spec.ts
index 2a3086771..04fe955cc 100644
--- a/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/NestedFunctionCallCompiler.spec.ts
+++ b/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/NestedFunctionCallCompiler.spec.ts
@@ -9,7 +9,9 @@ import { SingleCallCompilerStub } from '@tests/unit/shared/Stubs/SingleCallCompi
 import { CompiledCodeStub } from '@tests/unit/shared/Stubs/CompiledCodeStub';
 import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
 import type { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
-import { expectDeepThrowsError } from '@tests/shared/Assertions/ExpectDeepThrowsError';
+import { itThrowsContextualError } from '@tests/unit/application/Parser/ContextualErrorTester';
+import type { ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
+import { errorWithContextWrapperStub } from '@tests/unit/shared/Stubs/ErrorWithContextWrapperStub';
 
 describe('NestedFunctionCallCompiler', () => {
   describe('canCompile', () => {
@@ -43,12 +45,12 @@ describe('NestedFunctionCallCompiler', () => {
         // arrange
         const argumentCompiler = new ArgumentCompilerStub();
         const expectedContext = new FunctionCallCompilationContextStub();
-        const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
+        const { frontFunction, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
         const compiler = new NestedFunctionCallCompilerBuilder()
           .withArgumentCompiler(argumentCompiler)
           .build();
         // act
-        compiler.compileFunction(frontFunc, callToFrontFunc, expectedContext);
+        compiler.compileFunction(frontFunction, callToFrontFunc, expectedContext);
         // assert
         const calls = argumentCompiler.callHistory.filter((call) => call.methodName === 'createCompiledNestedCall');
         expect(calls).have.lengthOf(1);
@@ -59,33 +61,37 @@ describe('NestedFunctionCallCompiler', () => {
         // arrange
         const argumentCompiler = new ArgumentCompilerStub();
         const expectedContext = new FunctionCallCompilationContextStub();
-        const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
+        const { frontFunction, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
+        const expectedParentCall = callToFrontFunc;
         const compiler = new NestedFunctionCallCompilerBuilder()
           .withArgumentCompiler(argumentCompiler)
           .build();
         // act
-        compiler.compileFunction(frontFunc, callToFrontFunc, expectedContext);
+        compiler.compileFunction(frontFunction, callToFrontFunc, expectedContext);
         // assert
         const calls = argumentCompiler.callHistory.filter((call) => call.methodName === 'createCompiledNestedCall');
         expect(calls).have.lengthOf(1);
         const [,actualParentCall] = calls[0].args;
-        expect(actualParentCall).to.equal(callToFrontFunc);
+        expect(actualParentCall).to.equal(expectedParentCall);
       });
       it('uses correct nested call', () => {
         // arrange
         const argumentCompiler = new ArgumentCompilerStub();
         const expectedContext = new FunctionCallCompilationContextStub();
-        const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
+        const {
+          frontFunction, callToDeepFunc, callToFrontFunc,
+        } = createSingleFuncCallingAnotherFunc();
+        const expectedNestedCall = callToDeepFunc;
         const compiler = new NestedFunctionCallCompilerBuilder()
           .withArgumentCompiler(argumentCompiler)
           .build();
         // act
-        compiler.compileFunction(frontFunc, callToFrontFunc, expectedContext);
+        compiler.compileFunction(frontFunction, callToFrontFunc, expectedContext);
         // assert
         const calls = argumentCompiler.callHistory.filter((call) => call.methodName === 'createCompiledNestedCall');
         expect(calls).have.lengthOf(1);
         const [actualNestedCall] = calls[0].args;
-        expect(actualNestedCall).to.deep.equal(callToFrontFunc);
+        expect(actualNestedCall).to.deep.equal(expectedNestedCall);
       });
     });
     describe('re-compilation with compiled args', () => {
@@ -94,11 +100,11 @@ describe('NestedFunctionCallCompiler', () => {
         const singleCallCompilerStub = new SingleCallCompilerStub();
         const expectedContext = new FunctionCallCompilationContextStub()
           .withSingleCallCompiler(singleCallCompilerStub);
-        const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
+        const { frontFunction, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
         const compiler = new NestedFunctionCallCompilerBuilder()
           .build();
         // act
-        compiler.compileFunction(frontFunc, callToFrontFunc, expectedContext);
+        compiler.compileFunction(frontFunction, callToFrontFunc, expectedContext);
         // assert
         const calls = singleCallCompilerStub.callHistory.filter((call) => call.methodName === 'compileSingleCall');
         expect(calls).have.lengthOf(1);
@@ -113,12 +119,12 @@ describe('NestedFunctionCallCompiler', () => {
         const singleCallCompilerStub = new SingleCallCompilerStub();
         const context = new FunctionCallCompilationContextStub()
           .withSingleCallCompiler(singleCallCompilerStub);
-        const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
+        const { frontFunction, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
         const compiler = new NestedFunctionCallCompilerBuilder()
           .withArgumentCompiler(argumentCompilerStub)
           .build();
         // act
-        compiler.compileFunction(frontFunc, callToFrontFunc, context);
+        compiler.compileFunction(frontFunction, callToFrontFunc, context);
         // assert
         const calls = singleCallCompilerStub.callHistory.filter((call) => call.methodName === 'compileSingleCall');
         expect(calls).have.lengthOf(1);
@@ -140,9 +146,9 @@ describe('NestedFunctionCallCompiler', () => {
         .withScenario({ givenNestedFunctionCall: callToDeepFunc1, result: callToDeepFunc1 })
         .withScenario({ givenNestedFunctionCall: callToDeepFunc2, result: callToDeepFunc2 });
       const expectedFlattenedCodes = [...singleCallCompilationScenario.values()].flat();
-      const frontFunc = createSharedFunctionStubWithCalls()
+      const frontFunction = createSharedFunctionStubWithCalls()
         .withCalls(callToDeepFunc1, callToDeepFunc2);
-      const callToFrontFunc = new FunctionCallStub().withFunctionName(frontFunc.name);
+      const callToFrontFunc = new FunctionCallStub().withFunctionName(frontFunction.name);
       const singleCallCompilerStub = new SingleCallCompilerStub()
         .withCallCompilationScenarios(singleCallCompilationScenario);
       const expectedContext = new FunctionCallCompilationContextStub()
@@ -151,73 +157,105 @@ describe('NestedFunctionCallCompiler', () => {
         .withArgumentCompiler(argumentCompiler)
         .build();
       // act
-      const actualCodes = compiler.compileFunction(frontFunc, callToFrontFunc, expectedContext);
+      const actualCodes = compiler.compileFunction(frontFunction, callToFrontFunc, expectedContext);
       // assert
       expect(actualCodes).have.lengthOf(expectedFlattenedCodes.length);
       expect(actualCodes).to.have.members(expectedFlattenedCodes);
     });
     describe('error handling', () => {
-      it('handles argument compiler errors', () => {
+      describe('rethrows error from argument compiler', () => {
         // arrange
-        const argumentCompilerError = new Error('Test error');
+        const expectedInnerError = new Error(`Expected error from ${ArgumentCompilerStub.name}`);
+        const calleeFunctionName = 'expectedCalleeFunctionName';
+        const callerFunctionName = 'expectedCallerFunctionName';
+        const expectedErrorMessage = buildRethrowErrorMessage({
+          callee: calleeFunctionName,
+          caller: callerFunctionName,
+        });
+        const { frontFunction, callToFrontFunc } = createSingleFuncCallingAnotherFunc({
+          frontFunctionName: callerFunctionName,
+          deepFunctionName: calleeFunctionName,
+        });
         const argumentCompilerStub = new ArgumentCompilerStub();
         argumentCompilerStub.createCompiledNestedCall = () => {
-          throw argumentCompilerError;
+          throw expectedInnerError;
         };
-        const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
-        const expectedError = new AggregateError(
-          [argumentCompilerError],
-          `Error with call to "${callToFrontFunc.functionName}" function from "${callToFrontFunc.functionName}" function`,
-        );
-        const compiler = new NestedFunctionCallCompilerBuilder()
-          .withArgumentCompiler(argumentCompilerStub)
-          .build();
-        // act
-        const act = () => compiler.compileFunction(
-          frontFunc,
-          callToFrontFunc,
-          new FunctionCallCompilationContextStub(),
-        );
-        // assert
-        expectDeepThrowsError(act, expectedError);
+        const builder = new NestedFunctionCallCompilerBuilder()
+          .withArgumentCompiler(argumentCompilerStub);
+        itThrowsContextualError({
+          // act
+          throwingAction: (wrapError) => {
+            builder
+              .withErrorWrapper(wrapError)
+              .build()
+              .compileFunction(
+                frontFunction,
+                callToFrontFunc,
+                new FunctionCallCompilationContextStub(),
+              );
+          },
+          // assert
+          expectedWrappedError: expectedInnerError,
+          expectedContextMessage: expectedErrorMessage,
+        });
       });
-      it('handles single call compiler errors', () => {
+      describe('rethrows error from single call compiler', () => {
         // arrange
-        const singleCallCompilerError = new Error('Test error');
+        const expectedInnerError = new Error(`Expected error from ${SingleCallCompilerStub.name}`);
+        const calleeFunctionName = 'expectedCalleeFunctionName';
+        const callerFunctionName = 'expectedCallerFunctionName';
+        const expectedErrorMessage = buildRethrowErrorMessage({
+          callee: calleeFunctionName,
+          caller: callerFunctionName,
+        });
+        const { frontFunction, callToFrontFunc } = createSingleFuncCallingAnotherFunc({
+          frontFunctionName: callerFunctionName,
+          deepFunctionName: calleeFunctionName,
+        });
         const singleCallCompiler = new SingleCallCompilerStub();
         singleCallCompiler.compileSingleCall = () => {
-          throw singleCallCompilerError;
+          throw expectedInnerError;
         };
         const context = new FunctionCallCompilationContextStub()
           .withSingleCallCompiler(singleCallCompiler);
-        const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
-        const expectedError = new AggregateError(
-          [singleCallCompilerError],
-          `Error with call to "${callToFrontFunc.functionName}" function from "${callToFrontFunc.functionName}" function`,
-        );
-        const compiler = new NestedFunctionCallCompilerBuilder()
-          .build();
-        // act
-        const act = () => compiler.compileFunction(
-          frontFunc,
-          callToFrontFunc,
-          context,
-        );
-        // assert
-        expectDeepThrowsError(act, expectedError);
+        const builder = new NestedFunctionCallCompilerBuilder();
+        itThrowsContextualError({
+          // act
+          throwingAction: (wrapError) => {
+            builder
+              .withErrorWrapper(wrapError)
+              .build()
+              .compileFunction(
+                frontFunction,
+                callToFrontFunc,
+                context,
+              );
+          },
+          // assert
+          expectedWrappedError: expectedInnerError,
+          expectedContextMessage: expectedErrorMessage,
+        });
       });
     });
   });
 });
 
-function createSingleFuncCallingAnotherFunc() {
-  const deepFunc = createSharedFunctionStubWithCode();
-  const callToDeepFunc = new FunctionCallStub().withFunctionName(deepFunc.name);
-  const frontFunc = createSharedFunctionStubWithCalls().withCalls(callToDeepFunc);
-  const callToFrontFunc = new FunctionCallStub().withFunctionName(frontFunc.name);
+function createSingleFuncCallingAnotherFunc(
+  functionNames?: {
+    readonly frontFunctionName?: string;
+    readonly deepFunctionName?: string;
+  },
+) {
+  const deepFunction = createSharedFunctionStubWithCode()
+    .withName(functionNames?.deepFunctionName ?? 'deep-function (is called by front-function)');
+  const callToDeepFunc = new FunctionCallStub().withFunctionName(deepFunction.name);
+  const frontFunction = createSharedFunctionStubWithCalls()
+    .withCalls(callToDeepFunc)
+    .withName(functionNames?.frontFunctionName ?? 'front-function (calls deep-function)');
+  const callToFrontFunc = new FunctionCallStub().withFunctionName(frontFunction.name);
   return {
-    deepFunc,
-    frontFunc,
+    deepFunction,
+    frontFunction,
     callToFrontFunc,
     callToDeepFunc,
   };
@@ -226,14 +264,31 @@ function createSingleFuncCallingAnotherFunc() {
 class NestedFunctionCallCompilerBuilder {
   private argumentCompiler: ArgumentCompiler = new ArgumentCompilerStub();
 
+  private wrapError: ErrorWithContextWrapper = errorWithContextWrapperStub;
+
   public withArgumentCompiler(argumentCompiler: ArgumentCompiler): this {
     this.argumentCompiler = argumentCompiler;
     return this;
   }
 
+  public withErrorWrapper(wrapError: ErrorWithContextWrapper): this {
+    this.wrapError = wrapError;
+    return this;
+  }
+
   public build(): NestedFunctionCallCompiler {
     return new NestedFunctionCallCompiler(
       this.argumentCompiler,
+      this.wrapError,
     );
   }
 }
+
+function buildRethrowErrorMessage(
+  functionNames: {
+    readonly caller: string;
+    readonly callee: string;
+  },
+) {
+  return `Failed to call '${functionNames.callee}' (callee function) from '${functionNames.caller}' (caller function).`;
+}
diff --git a/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/AdaptiveFunctionCallCompiler.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/AdaptiveFunctionCallCompiler.spec.ts
index 08820d574..70b86529f 100644
--- a/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/AdaptiveFunctionCallCompiler.spec.ts
+++ b/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/AdaptiveFunctionCallCompiler.spec.ts
@@ -11,6 +11,7 @@ import type { FunctionCallCompilationContext } from '@/application/Parser/Script
 import { FunctionCallCompilationContextStub } from '@tests/unit/shared/Stubs/FunctionCallCompilationContextStub';
 import { CompiledCodeStub } from '@tests/unit/shared/Stubs/CompiledCodeStub';
 import type { SingleCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/SingleCallCompiler';
+import { collectExceptionMessage } from '@tests/unit/shared/ExceptionCollector';
 
 describe('AdaptiveFunctionCallCompiler', () => {
   describe('compileSingleCall', () => {
@@ -28,40 +29,40 @@ describe('AdaptiveFunctionCallCompiler', () => {
           functionParameters: ['expected-parameter'],
           callParameters: ['unexpected-parameter'],
           expectedError:
-            `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
-            + '. Expected parameter(s): "expected-parameter"',
+            `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter".`
+            + '\nExpected parameter(s): "expected-parameter"',
         },
         {
           description: 'provided: multiple unexpected parameters, when: different one is expected',
           functionParameters: ['expected-parameter'],
           callParameters: ['unexpected-parameter1', 'unexpected-parameter2'],
           expectedError:
-            `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter1", "unexpected-parameter2"`
-            + '. Expected parameter(s): "expected-parameter"',
+            `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter1", "unexpected-parameter2".`
+            + '\nExpected parameter(s): "expected-parameter"',
         },
         {
           description: 'provided: an unexpected parameter, when: multiple parameters are expected',
           functionParameters: ['expected-parameter1', 'expected-parameter2'],
           callParameters: ['expected-parameter1', 'expected-parameter2', 'unexpected-parameter'],
           expectedError:
-            `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
-            + '. Expected parameter(s): "expected-parameter1", "expected-parameter2"',
+            `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter".`
+            + '\nExpected parameter(s): "expected-parameter1", "expected-parameter2"',
         },
         {
           description: 'provided: an unexpected parameter, when: none required',
           functionParameters: [],
           callParameters: ['unexpected-call-parameter'],
           expectedError:
-            `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-call-parameter"`
-            + '. Expected parameter(s): none',
+            `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-call-parameter".`
+            + '\nExpected parameter(s): none',
         },
         {
           description: 'provided: expected and unexpected parameter, when: one of them is expected',
           functionParameters: ['expected-parameter'],
           callParameters: ['expected-parameter', 'unexpected-parameter'],
           expectedError:
-            `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
-            + '. Expected parameter(s): "expected-parameter"',
+            `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter".`
+            + '\nExpected parameter(s): "expected-parameter"',
         },
       ];
       testCases.forEach(({
@@ -88,7 +89,8 @@ describe('AdaptiveFunctionCallCompiler', () => {
           // act
           const act = () => builder.compileSingleCall();
           // assert
-          expect(act).to.throw(expectedError);
+          const errorMessage = collectExceptionMessage(act);
+          expect(errorMessage).to.include(expectedError);
         });
       });
     });
diff --git a/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.spec.ts
index 45752ab36..1a2e355c9 100644
--- a/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.spec.ts
+++ b/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.spec.ts
@@ -7,38 +7,44 @@ import type { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/
 import { ExpressionsCompilerStub } from '@tests/unit/shared/Stubs/ExpressionsCompilerStub';
 import { FunctionCallCompilationContextStub } from '@tests/unit/shared/Stubs/FunctionCallCompilationContextStub';
 import { FunctionCallStub } from '@tests/unit/shared/Stubs/FunctionCallStub';
-import { expectDeepThrowsError } from '@tests/shared/Assertions/ExpectDeepThrowsError';
 import { FunctionCallArgumentCollectionStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentCollectionStub';
 import { createSharedFunctionStubWithCode } from '@tests/unit/shared/Stubs/SharedFunctionStub';
 import { FunctionParameterCollectionStub } from '@tests/unit/shared/Stubs/FunctionParameterCollectionStub';
 import { SharedFunctionCollectionStub } from '@tests/unit/shared/Stubs/SharedFunctionCollectionStub';
+import { itThrowsContextualError } from '@tests/unit/application/Parser/ContextualErrorTester';
+import type { ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
+import { errorWithContextWrapperStub } from '@tests/unit/shared/Stubs/ErrorWithContextWrapperStub';
 
 describe('NestedFunctionArgumentCompiler', () => {
   describe('createCompiledNestedCall', () => {
-    it('should handle error from expressions compiler', () => {
+    describe('rethrows error from expressions compiler', () => {
       // arrange
+      const expectedInnerError = new Error('child-');
       const parameterName = 'parameterName';
+      const expectedErrorMessage = `Error when compiling argument for "${parameterName}"`;
       const nestedCall = new FunctionCallStub()
         .withFunctionName('nested-function-call')
         .withArgumentCollection(new FunctionCallArgumentCollectionStub()
           .withArgument(parameterName, 'unimportant-value'));
       const parentCall = new FunctionCallStub()
         .withFunctionName('parent-function-call');
-      const expressionsCompilerError = new Error('child-');
-      const expectedError = new AggregateError(
-        [expressionsCompilerError],
-        `Error when compiling argument for "${parameterName}"`,
-      );
       const expressionsCompiler = new ExpressionsCompilerStub();
-      expressionsCompiler.compileExpressions = () => { throw expressionsCompilerError; };
+      expressionsCompiler.compileExpressions = () => { throw expectedInnerError; };
       const builder = new NestedFunctionArgumentCompilerBuilder()
         .withParentFunctionCall(parentCall)
         .withNestedFunctionCall(nestedCall)
         .withExpressionsCompiler(expressionsCompiler);
-      // act
-      const act = () => builder.createCompiledNestedCall();
-      // assert
-      expectDeepThrowsError(act, expectedError);
+      itThrowsContextualError({
+        // act
+        throwingAction: (wrapError) => {
+          builder
+            .withErrorWrapper(wrapError)
+            .createCompiledNestedCall();
+        },
+        // assert
+        expectedWrappedError: expectedInnerError,
+        expectedContextMessage: expectedErrorMessage,
+      });
     });
     describe('compilation', () => {
       describe('without arguments', () => {
@@ -258,6 +264,8 @@ class NestedFunctionArgumentCompilerBuilder implements ArgumentCompiler {
 
   private context: FunctionCallCompilationContext = new FunctionCallCompilationContextStub();
 
+  private wrapError: ErrorWithContextWrapper = errorWithContextWrapperStub;
+
   public withExpressionsCompiler(expressionsCompiler: IExpressionsCompiler): this {
     this.expressionsCompiler = expressionsCompiler;
     return this;
@@ -278,8 +286,16 @@ class NestedFunctionArgumentCompilerBuilder implements ArgumentCompiler {
     return this;
   }
 
+  public withErrorWrapper(wrapError: ErrorWithContextWrapper): this {
+    this.wrapError = wrapError;
+    return this;
+  }
+
   public createCompiledNestedCall(): FunctionCall {
-    const compiler = new NestedFunctionArgumentCompiler(this.expressionsCompiler);
+    const compiler = new NestedFunctionArgumentCompiler(
+      this.expressionsCompiler,
+      this.wrapError,
+    );
     return compiler.createCompiledNestedCall(
       this.nestedFunctionCall,
       this.parentFunctionCall,
diff --git a/tests/unit/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection.spec.ts
index 6a628ee43..78eee79ee 100644
--- a/tests/unit/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection.spec.ts
+++ b/tests/unit/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection.spec.ts
@@ -7,8 +7,8 @@ describe('FunctionParameterCollection', () => {
     // arrange
     const expected = [
       new FunctionParameterStub().withName('1'),
-      new FunctionParameterStub().withName('2').withOptionality(true),
-      new FunctionParameterStub().withName('3').withOptionality(false),
+      new FunctionParameterStub().withName('2').withOptional(true),
+      new FunctionParameterStub().withName('3').withOptional(false),
     ];
     const sut = new FunctionParameterCollection();
     for (const parameter of expected) {
diff --git a/tests/unit/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollectionFactory.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollectionFactory.spec.ts
new file mode 100644
index 000000000..99ad4ec43
--- /dev/null
+++ b/tests/unit/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollectionFactory.spec.ts
@@ -0,0 +1,23 @@
+import { describe, it, expect } from 'vitest';
+import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
+import { createFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollectionFactory';
+import { itIsTransientFactory } from '@tests/unit/shared/TestCases/TransientFactoryTests';
+
+describe('FunctionParameterCollectionFactory', () => {
+  describe('createFunctionParameterCollection', () => {
+    describe('it is a transient factory', () => {
+      itIsTransientFactory({
+        getter: () => createFunctionParameterCollection(),
+        expectedType: FunctionParameterCollection,
+      });
+    });
+    it('returns an empty collection', () => {
+      // arrange
+      const expectedInitialParametersCount = 0;
+      // act
+      const collection = createFunctionParameterCollection();
+      // assert
+      expect(collection.all).to.have.lengthOf(expectedInitialParametersCount);
+    });
+  });
+});
diff --git a/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionCollection.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionCollection.spec.ts
index 87fdba86c..f5019f2ba 100644
--- a/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionCollection.spec.ts
+++ b/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionCollection.spec.ts
@@ -35,7 +35,7 @@ describe('SharedFunctionCollection', () => {
     it('throws if function does not exist', () => {
       // arrange
       const name = 'unique-name';
-      const expectedError = `called function is not defined "${name}"`;
+      const expectedError = `Called function is not defined: "${name}"`;
       const func = createSharedFunctionStubWithCode()
         .withName('unexpected-name');
       const sut = new SharedFunctionCollection();
diff --git a/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionsParser.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionsParser.spec.ts
index 29426618e..85c685a81 100644
--- a/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionsParser.spec.ts
+++ b/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionsParser.spec.ts
@@ -1,25 +1,29 @@
 import { describe, it, expect } from 'vitest';
 import type { FunctionData, CodeInstruction } from '@/application/collections/';
 import type { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
-import { SharedFunctionsParser } from '@/application/Parser/Script/Compiler/Function/SharedFunctionsParser';
+import { SharedFunctionsParser, type FunctionParameterFactory } from '@/application/Parser/Script/Compiler/Function/SharedFunctionsParser';
 import { createFunctionDataWithCode, createFunctionDataWithoutCallOrCode } from '@tests/unit/shared/Stubs/FunctionDataStub';
 import { ParameterDefinitionDataStub } from '@tests/unit/shared/Stubs/ParameterDefinitionDataStub';
-import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
 import { FunctionCallDataStub } from '@tests/unit/shared/Stubs/FunctionCallDataStub';
 import { itEachAbsentCollectionValue } from '@tests/unit/shared/TestCases/AbsentTests';
 import type { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syntax/ILanguageSyntax';
 import { LanguageSyntaxStub } from '@tests/unit/shared/Stubs/LanguageSyntaxStub';
-import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
+import { itIsSingletonFactory } from '@tests/unit/shared/TestCases/SingletonFactoryTests';
 import { CodeValidatorStub } from '@tests/unit/shared/Stubs/CodeValidatorStub';
 import type { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
 import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmptyLines';
 import { NoDuplicatedLines } from '@/application/Parser/Script/Validation/Rules/NoDuplicatedLines';
-import { collectExceptionMessage } from '@tests/unit/shared/ExceptionCollector';
+import type { ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
+import { FunctionParameterStub } from '@tests/unit/shared/Stubs/FunctionParameterStub';
+import { errorWithContextWrapperStub } from '@tests/unit/shared/Stubs/ErrorWithContextWrapperStub';
+import { itThrowsContextualError } from '@tests/unit/application/Parser/ContextualErrorTester';
+import type { FunctionParameterCollectionFactory } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollectionFactory';
+import { FunctionParameterCollectionStub } from '@tests/unit/shared/Stubs/FunctionParameterCollectionStub';
 import { expectCallsFunctionBody, expectCodeFunctionBody } from './ExpectFunctionBodyType';
 
 describe('SharedFunctionsParser', () => {
   describe('instance', () => {
-    itIsSingleton({
+    itIsSingletonFactory({
       getter: () => SharedFunctionsParser.instance,
       expectedType: SharedFunctionsParser,
     });
@@ -127,7 +131,7 @@ describe('SharedFunctionsParser', () => {
         });
       });
       describe('throws when parameters type is not as expected', () => {
-        const testCases = [
+        const testScenarios = [
           {
             state: 'when not an array',
             invalidType: 5,
@@ -137,7 +141,7 @@ describe('SharedFunctionsParser', () => {
             invalidType: ['a', { a: 'b' }],
           },
         ];
-        for (const testCase of testCases) {
+        for (const testCase of testScenarios) {
           it(testCase.state, () => {
             // arrange
             const func = createFunctionDataWithCode()
@@ -170,25 +174,37 @@ describe('SharedFunctionsParser', () => {
           rules: expectedRules,
         });
       });
-      it('rethrows including function name when FunctionParameter throws', () => {
-        // arrange
-        const invalidParameterName = 'invalid function p@r4meter name';
-        const functionName = 'functionName';
-        const message = collectExceptionMessage(
-          () => new FunctionParameter(invalidParameterName, false),
-        );
-        const expectedError = `"${functionName}": ${message}`;
-        const functionData = createFunctionDataWithCode()
-          .withName(functionName)
-          .withParameters(new ParameterDefinitionDataStub().withName(invalidParameterName));
-
-        // act
-        const act = () => new ParseFunctionsCallerWithDefaults()
-          .withFunctions([functionData])
-          .parseFunctions();
-
-        // assert
-        expect(act).to.throw(expectedError);
+      describe('parameter creation', () => {
+        // TODO: Factory tests, creates with given name
+        // TODO: Factory tests, creates with given optional:false
+        // TODO: Factory tests, creates with given optional:true
+        // TODO: Factory tests, creates with given optional:undefined
+        describe('rethrows including function name when creating parameter throws', () => {
+          // arrange
+          const invalidParameterName = 'invalid-function-parameter-name';
+          const functionName = 'functionName';
+          const expectedErrorMessage = `Failed to create parameter: ${invalidParameterName} for function "${functionName}"`;
+          const expectedInnerError = new Error('injected error');
+          const parameterFactory: FunctionParameterFactory = () => {
+            throw expectedInnerError;
+          };
+          const functionData = createFunctionDataWithCode()
+            .withName(functionName)
+            .withParameters(new ParameterDefinitionDataStub().withName(invalidParameterName));
+          itThrowsContextualError({
+            // act
+            throwingAction: (wrapError) => {
+              new ParseFunctionsCallerWithDefaults()
+                .withFunctions([functionData])
+                .withFunctionParameterFactory(parameterFactory)
+                .withErrorWrapper(wrapError)
+                .parseFunctions();
+            },
+            // assert
+            expectedWrappedError: expectedInnerError,
+            expectedContextMessage: expectedErrorMessage,
+          });
+        });
       });
     });
     describe('given empty functions, returns empty collection', () => {
@@ -282,6 +298,18 @@ class ParseFunctionsCallerWithDefaults {
 
   private functions: readonly FunctionData[] = [createFunctionDataWithCode()];
 
+  private wrapError: ErrorWithContextWrapper = errorWithContextWrapperStub;
+
+  private parameterFactory: FunctionParameterFactory = (
+    name: string,
+    isOptional: boolean,
+  ) => new FunctionParameterStub()
+    .withName(name)
+    .withOptional(isOptional);
+
+  private parameterCollectionFactory
+  : FunctionParameterCollectionFactory = () => new FunctionParameterCollectionStub();
+
   public withSyntax(syntax: ILanguageSyntax) {
     this.syntax = syntax;
     return this;
@@ -297,8 +325,32 @@ class ParseFunctionsCallerWithDefaults {
     return this;
   }
 
+  public withErrorWrapper(wrapError: ErrorWithContextWrapper): this {
+    this.wrapError = wrapError;
+    return this;
+  }
+
+  public withFunctionParameterFactory(parameterFactory: FunctionParameterFactory): this {
+    this.parameterFactory = parameterFactory;
+    return this;
+  }
+
+  public withParameterCollectionFactory(
+    parameterCollectionFactory: FunctionParameterCollectionFactory,
+  ): this {
+    this.parameterCollectionFactory = parameterCollectionFactory;
+    return this;
+  }
+
   public parseFunctions() {
-    const sut = new SharedFunctionsParser(this.codeValidator);
+    const sut = new SharedFunctionsParser(
+      {
+        codeValidator: this.codeValidator,
+        wrapError: this.wrapError,
+        createParameter: this.parameterFactory,
+        createParameterCollection: this.parameterCollectionFactory,
+      },
+    );
     return sut.parseFunctions(this.functions, this.syntax);
   }
 }
diff --git a/tests/unit/application/Parser/Script/Compiler/ScriptCompiler.spec.ts b/tests/unit/application/Parser/Script/Compiler/ScriptCompiler.spec.ts
index 43c096ae8..5a35b8bbe 100644
--- a/tests/unit/application/Parser/Script/Compiler/ScriptCompiler.spec.ts
+++ b/tests/unit/application/Parser/Script/Compiler/ScriptCompiler.spec.ts
@@ -1,9 +1,7 @@
 import { describe, it, expect } from 'vitest';
 import type { FunctionData } from '@/application/collections/';
-import { ScriptCode } from '@/domain/ScriptCode';
 import { ScriptCompiler } from '@/application/Parser/Script/Compiler/ScriptCompiler';
 import type { ISharedFunctionsParser } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionsParser';
-import type { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
 import type { FunctionCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler';
 import { LanguageSyntaxStub } from '@tests/unit/shared/Stubs/LanguageSyntaxStub';
 import { createFunctionDataWithCode } from '@tests/unit/shared/Stubs/FunctionDataStub';
@@ -17,8 +15,13 @@ import type { ICodeValidator } from '@/application/Parser/Script/Validation/ICod
 import { CodeValidatorStub } from '@tests/unit/shared/Stubs/CodeValidatorStub';
 import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmptyLines';
 import { CompiledCodeStub } from '@tests/unit/shared/Stubs/CompiledCodeStub';
-import { collectExceptionMessage } from '@tests/unit/shared/ExceptionCollector';
 import { createScriptDataWithCall, createScriptDataWithCode } from '@tests/unit/shared/Stubs/ScriptDataStub';
+import type { ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
+import { errorWithContextWrapperStub } from '@tests/unit/shared/Stubs/ErrorWithContextWrapperStub';
+import { ScriptCodeStub } from '@tests/unit/shared/Stubs/ScriptCodeStub';
+import type { ScriptCodeFactory } from '@/domain/ScriptCodeFactory';
+import { createScriptCodeFactoryStub } from '@tests/unit/shared/Stubs/ScriptCodeFactoryStub';
+import { itThrowsContextualError } from '../../ContextualErrorTester';
 
 describe('ScriptCompiler', () => {
   describe('canCompile', () => {
@@ -58,31 +61,59 @@ describe('ScriptCompiler', () => {
       // assert
       expect(act).to.throw(expectedError);
     });
-    it('returns code as expected', () => {
-      // arrange
-      const expected: CompiledCode = {
-        code: 'expected-code',
-        revertCode: 'expected-revert-code',
-      };
-      const call = new FunctionCallDataStub();
-      const script = createScriptDataWithCall(call);
-      const functions = [createFunctionDataWithCode().withName('existing-func')];
-      const compiledFunctions = new SharedFunctionCollectionStub();
-      const functionParserMock = new SharedFunctionsParserStub();
-      functionParserMock.setup(functions, compiledFunctions);
-      const callCompilerMock = new FunctionCallCompilerStub();
-      callCompilerMock.setup(parseFunctionCalls(call), compiledFunctions, expected);
-      const sut = new ScriptCompilerBuilder()
-        .withFunctions(...functions)
-        .withSharedFunctionsParser(functionParserMock)
-        .withFunctionCallCompiler(callCompilerMock)
-        .build();
-      // act
-      const code = sut.compile(script);
-      // assert
-      expect(code.execute).to.equal(expected.code);
-      expect(code.revert).to.equal(expected.revertCode);
+    describe('code construction', () => {
+      it('returns code from the factory', () => {
+        // arrange
+        const expectedCode = new ScriptCodeStub();
+        const scriptCodeFactory = () => expectedCode;
+        const sut = new ScriptCompilerBuilder()
+          .withSomeFunctions()
+          .withScriptCodeFactory(scriptCodeFactory)
+          .build();
+        // act
+        const actualCode = sut.compile(createScriptDataWithCall());
+        // assert
+        expect(actualCode).to.equal(expectedCode);
+      });
+      it('creates code correctly', () => {
+        // arrange
+        const expectedCode = 'expected-code';
+        const expectedRevertCode = 'expected-revert-code';
+        let actualCode: string | undefined;
+        let actualRevertCode: string | undefined;
+        const scriptCodeFactory = (code: string, revertCode: string) => {
+          actualCode = code;
+          actualRevertCode = revertCode;
+          return new ScriptCodeStub();
+        };
+        const call = new FunctionCallDataStub();
+        const script = createScriptDataWithCall(call);
+        const functions = [createFunctionDataWithCode().withName('existing-func')];
+        const compiledFunctions = new SharedFunctionCollectionStub();
+        const functionParserMock = new SharedFunctionsParserStub();
+        functionParserMock.setup(functions, compiledFunctions);
+        const callCompilerMock = new FunctionCallCompilerStub();
+        callCompilerMock.setup(
+          parseFunctionCalls(call),
+          compiledFunctions,
+          new CompiledCodeStub()
+            .withCode(expectedCode)
+            .withRevertCode(expectedRevertCode),
+        );
+        const sut = new ScriptCompilerBuilder()
+          .withFunctions(...functions)
+          .withSharedFunctionsParser(functionParserMock)
+          .withFunctionCallCompiler(callCompilerMock)
+          .withScriptCodeFactory(scriptCodeFactory)
+          .build();
+        // act
+        sut.compile(script);
+        // assert
+        expect(actualCode).to.equal(expectedCode);
+        expect(actualRevertCode).to.equal(expectedRevertCode);
+      });
     });
+
     describe('parses functions as expected', () => {
       it('parses functions with expected syntax', () => {
         // arrange
@@ -116,49 +147,57 @@ describe('ScriptCompiler', () => {
         expect(parser.callHistory[0].functions).to.deep.equal(expectedFunctions);
       });
     });
-    it('rethrows error with script name', () => {
+    describe('rethrows error with script name', () => {
       // arrange
       const scriptName = 'scriptName';
-      const innerError = 'innerError';
-      const expectedError = `Script "${scriptName}" ${innerError}`;
+      const expectedErrorMessage = `Failed to compile script: ${scriptName}`;
+      const expectedInnerError = new Error();
       const callCompiler: FunctionCallCompiler = {
-        compileFunctionCalls: () => { throw new Error(innerError); },
+        compileFunctionCalls: () => { throw expectedInnerError; },
       };
       const scriptData = createScriptDataWithCall()
         .withName(scriptName);
-      const sut = new ScriptCompilerBuilder()
+      const builder = new ScriptCompilerBuilder()
         .withSomeFunctions()
-        .withFunctionCallCompiler(callCompiler)
-        .build();
-      // act
-      const act = () => sut.compile(scriptData);
-      // assert
-      expect(act).to.throw(expectedError);
+        .withFunctionCallCompiler(callCompiler);
+      itThrowsContextualError({
+        // act
+        throwingAction: (wrapError) => {
+          builder
+            .withErrorWrapper(wrapError)
+            .build()
+            .compile(scriptData);
+        },
+        // assert
+        expectedWrappedError: expectedInnerError,
+        expectedContextMessage: expectedErrorMessage,
+      });
     });
-    it('rethrows error from ScriptCode with script name', () => {
+    describe('rethrows error from script code factory with script name', () => {
       // arrange
       const scriptName = 'scriptName';
-      const syntax = new LanguageSyntaxStub();
-      const invalidCode = new CompiledCodeStub()
-        .withCode('' /* invalid code (empty string) */);
-      const realExceptionMessage = collectExceptionMessage(
-        () => new ScriptCode(invalidCode.code, invalidCode.revertCode),
-      );
-      const expectedError = `Script "${scriptName}" ${realExceptionMessage}`;
-      const callCompiler: FunctionCallCompiler = {
-        compileFunctionCalls: () => invalidCode,
+      const expectedErrorMessage = `Failed to compile script: ${scriptName}`;
+      const expectedInnerError = new Error();
+      const scriptCodeFactory: ScriptCodeFactory = () => {
+        throw expectedInnerError;
       };
       const scriptData = createScriptDataWithCall()
         .withName(scriptName);
-      const sut = new ScriptCompilerBuilder()
+      const builder = new ScriptCompilerBuilder()
         .withSomeFunctions()
-        .withFunctionCallCompiler(callCompiler)
-        .withSyntax(syntax)
-        .build();
-      // act
-      const act = () => sut.compile(scriptData);
-      // assert
-      expect(act).to.throw(expectedError);
+        .withScriptCodeFactory(scriptCodeFactory);
+      itThrowsContextualError({
+        // act
+        throwingAction: (wrapError) => {
+          builder
+            .withErrorWrapper(wrapError)
+            .build()
+            .compile(scriptData);
+        },
+        // assert
+        expectedWrappedError: expectedInnerError,
+        expectedContextMessage: expectedErrorMessage,
+      });
     });
     it('validates compiled code as expected', () => {
       // arrange
@@ -166,17 +205,27 @@ describe('ScriptCompiler', () => {
         NoEmptyLines,
         // Allow duplicated lines to enable calling same function multiple times
       ];
+      const expectedExecuteCode = 'execute code to be validated';
+      const expectedRevertCode = 'revert code to be validated';
       const scriptData = createScriptDataWithCall();
       const validator = new CodeValidatorStub();
       const sut = new ScriptCompilerBuilder()
         .withSomeFunctions()
         .withCodeValidator(validator)
+        .withFunctionCallCompiler(
+          new FunctionCallCompilerStub()
+            .withDefaultCompiledCode(
+              new CompiledCodeStub()
+                .withCode(expectedExecuteCode)
+                .withRevertCode(expectedRevertCode),
+            ),
+        )
         .build();
       // act
-      const compilationResult = sut.compile(scriptData);
+      sut.compile(scriptData);
       // assert
       validator.assertHistory({
-        validatedCodes: [compilationResult.execute, compilationResult.revert],
+        validatedCodes: [expectedExecuteCode, expectedRevertCode],
         rules: expectedRules,
       });
     });
@@ -200,6 +249,12 @@ class ScriptCompilerBuilder {
 
   private codeValidator: ICodeValidator = new CodeValidatorStub();
 
+  private wrapError: ErrorWithContextWrapper = errorWithContextWrapperStub;
+
+  private scriptCodeFactory: ScriptCodeFactory = createScriptCodeFactoryStub({
+    defaultCodePrefix: ScriptCompilerBuilder.name,
+  });
+
   public withFunctions(...functions: FunctionData[]): this {
     this.functions = functions;
     return this;
@@ -244,6 +299,16 @@ class ScriptCompilerBuilder {
     return this;
   }
 
+  public withErrorWrapper(wrapError: ErrorWithContextWrapper): this {
+    this.wrapError = wrapError;
+    return this;
+  }
+
+  public withScriptCodeFactory(scriptCodeFactory: ScriptCodeFactory): this {
+    this.scriptCodeFactory = scriptCodeFactory;
+    return this;
+  }
+
   public build(): ScriptCompiler {
     if (!this.functions) {
       throw new Error('Function behavior not defined');
@@ -254,6 +319,8 @@ class ScriptCompilerBuilder {
       this.sharedFunctionsParser,
       this.callCompiler,
       this.codeValidator,
+      this.wrapError,
+      this.scriptCodeFactory,
     );
   }
 }
diff --git a/tests/unit/application/Parser/Script/ScriptParser.spec.ts b/tests/unit/application/Parser/Script/ScriptParser.spec.ts
index 0152981c5..db18fa282 100644
--- a/tests/unit/application/Parser/Script/ScriptParser.spec.ts
+++ b/tests/unit/application/Parser/Script/ScriptParser.spec.ts
@@ -1,7 +1,7 @@
 import { describe, it, expect } from 'vitest';
 import type { ScriptData } from '@/application/collections/';
-import { parseScript, type ScriptFactoryType } from '@/application/Parser/Script/ScriptParser';
-import { parseDocs } from '@/application/Parser/DocumentationParser';
+import { parseScript, type ScriptFactory } from '@/application/Parser/Script/ScriptParser';
+import { type DocsParser } from '@/application/Parser/DocumentationParser';
 import { RecommendationLevel } from '@/domain/RecommendationLevel';
 import type { ICategoryCollectionParseContext } from '@/application/Parser/Script/ICategoryCollectionParseContext';
 import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
@@ -11,54 +11,88 @@ import { EnumParserStub } from '@tests/unit/shared/Stubs/EnumParserStub';
 import { ScriptCodeStub } from '@tests/unit/shared/Stubs/ScriptCodeStub';
 import { CategoryCollectionParseContextStub } from '@tests/unit/shared/Stubs/CategoryCollectionParseContextStub';
 import { LanguageSyntaxStub } from '@tests/unit/shared/Stubs/LanguageSyntaxStub';
-import { NodeType } from '@/application/Parser/NodeValidation/NodeType';
-import { expectThrowsNodeError, type ITestScenario, NodeValidationTestRunner } from '@tests/unit/application/Parser/NodeValidation/NodeValidatorTestRunner';
 import { Script } from '@/domain/Script';
 import type { IEnumParser } from '@/application/Common/Enum';
 import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmptyLines';
 import { NoDuplicatedLines } from '@/application/Parser/Script/Validation/Rules/NoDuplicatedLines';
 import { CodeValidatorStub } from '@tests/unit/shared/Stubs/CodeValidatorStub';
 import type { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
+import type { ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
+import { ErrorWrapperStub } from '@tests/unit/shared/Stubs/ErrorWrapperStub';
+import type { NodeDataValidatorFactory } from '@/application/Parser/NodeValidation/NodeDataValidator';
+import { NodeDataValidatorStub, createNodeDataValidatorFactoryStub } from '@tests/unit/shared/Stubs/NodeDataValidatorStub';
+import { NodeDataType } from '@/application/Parser/NodeValidation/NodeDataType';
+import type { ScriptNodeErrorContext } from '@/application/Parser/NodeValidation/NodeDataErrorContext';
+import type { ScriptCodeFactory } from '@/domain/ScriptCodeFactory';
+import { createScriptCodeFactoryStub } from '@tests/unit/shared/Stubs/ScriptCodeFactoryStub';
+import { ScriptStub } from '@tests/unit/shared/Stubs/ScriptStub';
+import { createScriptFactorySpy } from '@tests/unit/shared/Stubs/ScriptFactoryStub';
+import { expectExists } from '@tests/shared/Assertions/ExpectExists';
+import { itThrowsContextualError } from '../ContextualErrorTester';
+import { itAsserts, itValidatesDefinedData, itValidatesName } from '../NodeDataValidationTester';
+import { generateDataValidationTestScenarios } from '../DataValidationTestScenarioGenerator';
 
 describe('ScriptParser', () => {
   describe('parseScript', () => {
-    it('parses name as expected', () => {
+    it('parses name correctly', () => {
       // arrange
       const expected = 'test-expected-name';
-      const script = createScriptDataWithCode()
+      const scriptData = createScriptDataWithCode()
         .withName(expected);
+      const { scriptFactorySpy, getInitParameters } = createScriptFactorySpy();
       // act
-      const actual = new TestBuilder()
-        .withData(script)
+      const actualScript = new TestContext()
+        .withData(scriptData)
+        .withScriptFactory(scriptFactorySpy)
         .parseScript();
       // assert
-      expect(actual.name).to.equal(expected);
+      const actualName = getInitParameters(actualScript)?.name;
+      expect(actualName).to.equal(expected);
     });
-    it('parses docs as expected', () => {
+    it('parses docs correctly', () => {
       // arrange
-      const docs = ['https://expected-doc1.com', 'https://expected-doc2.com'];
-      const script = createScriptDataWithCode()
-        .withDocs(docs);
-      const expected = parseDocs(script);
+      const expectedDocs = ['https://expected-doc1.com', 'https://expected-doc2.com'];
+      const { scriptFactorySpy, getInitParameters } = createScriptFactorySpy();
+      const scriptData = createScriptDataWithCode()
+        .withDocs(expectedDocs);
+      const docsParser: DocsParser = (data) => data.docs as typeof expectedDocs;
       // act
-      const actual = new TestBuilder()
-        .withData(script)
+      const actualScript = new TestContext()
+        .withData(scriptData)
+        .withScriptFactory(scriptFactorySpy)
+        .withDocsParser(docsParser)
         .parseScript();
       // assert
-      expect(actual.docs).to.deep.equal(expected);
+      const actualDocs = getInitParameters(actualScript)?.docs;
+      expect(actualDocs).to.deep.equal(expectedDocs);
+    });
+    it('gets script from the factory', () => {
+      // arrange
+      const expectedScript = new ScriptStub('expected-script');
+      const scriptFactory: ScriptFactory = () => expectedScript;
+      // act
+      const actualScript = new TestContext()
+        .withScriptFactory(scriptFactory)
+        .parseScript();
+      // assert
+      expect(actualScript).to.equal(expectedScript);
     });
     describe('level', () => {
-      describe('accepts absent level', () => {
+      describe('generated `undefined` level if given absent value', () => {
         itEachAbsentStringValue((absentValue) => {
           // arrange
-          const script = createScriptDataWithCode()
+          const expectedLevel = undefined;
+          const scriptData = createScriptDataWithCode()
             .withRecommend(absentValue);
+          const { scriptFactorySpy, getInitParameters } = createScriptFactorySpy();
           // act
-          const actual = new TestBuilder()
-            .withData(script)
+          const actualScript = new TestContext()
+            .withData(scriptData)
+            .withScriptFactory(scriptFactorySpy)
             .parseScript();
           // assert
-          expect(actual.level).to.equal(undefined);
+          const actualLevel = getInitParameters(actualScript)?.level;
+          expect(actualLevel).to.equal(expectedLevel);
         }, { excludeNull: true });
       });
       it('parses level as expected', () => {
@@ -66,63 +100,94 @@ describe('ScriptParser', () => {
         const expectedLevel = RecommendationLevel.Standard;
         const expectedName = 'level';
         const levelText = 'standard';
-        const script = createScriptDataWithCode()
+        const scriptData = createScriptDataWithCode()
           .withRecommend(levelText);
         const parserMock = new EnumParserStub<RecommendationLevel>()
           .setup(expectedName, levelText, expectedLevel);
+        const { scriptFactorySpy, getInitParameters } = createScriptFactorySpy();
         // act
-        const actual = new TestBuilder()
-          .withData(script)
+        const actualScript = new TestContext()
+          .withData(scriptData)
           .withParser(parserMock)
+          .withScriptFactory(scriptFactorySpy)
           .parseScript();
         // assert
-        expect(actual.level).to.equal(expectedLevel);
+        const actualLevel = getInitParameters(actualScript)?.level;
+        expect(actualLevel).to.equal(expectedLevel);
       });
     });
     describe('code', () => {
-      it('parses "execute" as expected', () => {
+      it('creates from script code factory', () => {
         // arrange
-        const expected = 'expected-code';
-        const script = createScriptDataWithCode()
-          .withCode(expected);
+        const expectedCode = new ScriptCodeStub();
+        const scriptCodeFactory: ScriptCodeFactory = () => expectedCode;
+        const { scriptFactorySpy, getInitParameters } = createScriptFactorySpy();
         // act
-        const parsed = new TestBuilder()
-          .withData(script)
+        const actualScript = new TestContext()
+          .withScriptCodeFactory(scriptCodeFactory)
+          .withScriptFactory(scriptFactorySpy)
           .parseScript();
         // assert
-        const actual = parsed.code.execute;
-        expect(actual).to.equal(expected);
+        const actualCode = getInitParameters(actualScript)?.code;
+        expect(expectedCode).to.equal(actualCode);
       });
-      it('parses "revert" as expected', () => {
-        // arrange
-        const expected = 'expected-revert-code';
-        const script = createScriptDataWithCode()
-          .withRevertCode(expected);
-        // act
-        const parsed = new TestBuilder()
-          .withData(script)
-          .parseScript();
-        // assert
-        const actual = parsed.code.revert;
-        expect(actual).to.equal(expected);
+      describe('parses code correctly', () => {
+        it('parses "execute" as expected', () => {
+          // arrange
+          const expectedCode = 'expected-code';
+          let actualCode: string | undefined;
+          const scriptCodeFactory: ScriptCodeFactory = (code) => {
+            actualCode = code;
+            return new ScriptCodeStub();
+          };
+          const scriptData = createScriptDataWithCode()
+            .withCode(expectedCode);
+          // act
+          new TestContext()
+            .withData(scriptData)
+            .withScriptCodeFactory(scriptCodeFactory)
+            .parseScript();
+          // assert
+          expect(actualCode).to.equal(expectedCode);
+        });
+        it('parses "revert" as expected', () => {
+          // arrange
+          const expectedRevertCode = 'expected-revert-code';
+          const scriptData = createScriptDataWithCode()
+            .withRevertCode(expectedRevertCode);
+          let actualRevertCode: string | undefined;
+          const scriptCodeFactory: ScriptCodeFactory = (_, revertCode) => {
+            actualRevertCode = revertCode;
+            return new ScriptCodeStub();
+          };
+          // act
+          new TestContext()
+            .withData(scriptData)
+            .withScriptCodeFactory(scriptCodeFactory)
+            .parseScript();
+          // assert
+          expect(actualRevertCode).to.equal(expectedRevertCode);
+        });
       });
       describe('compiler', () => {
-        it('gets code from compiler', () => {
+        it('compiles the code through the compiler', () => {
           // arrange
-          const expected = new ScriptCodeStub();
+          const expectedCode = new ScriptCodeStub();
           const script = createScriptDataWithCode();
           const compiler = new ScriptCompilerStub()
-            .withCompileAbility(script, expected);
+            .withCompileAbility(script, expectedCode);
           const parseContext = new CategoryCollectionParseContextStub()
             .withCompiler(compiler);
+          const { scriptFactorySpy, getInitParameters } = createScriptFactorySpy();
           // act
-          const parsed = new TestBuilder()
+          const actualScript = new TestContext()
             .withData(script)
             .withContext(parseContext)
+            .withScriptFactory(scriptFactorySpy)
             .parseScript();
           // assert
-          const actual = parsed.code;
-          expect(actual).to.equal(expected);
+          const actualCode = getInitParameters(actualScript)?.code;
+          expect(actualCode).to.equal(expectedCode);
         });
       });
       describe('syntax', () => {
@@ -135,7 +200,7 @@ describe('ScriptParser', () => {
           const script = createScriptDataWithoutCallOrCodes()
             .withCode(duplicatedCode);
           // act
-          const act = () => new TestBuilder()
+          const act = () => new TestContext()
             .withData(script)
             .withContext(parseContext);
           // assert
@@ -149,18 +214,26 @@ describe('ScriptParser', () => {
             NoEmptyLines,
             NoDuplicatedLines,
           ];
+          const expectedCode = 'expected code to be validated';
+          const expectedRevertCode = 'expected revert code to be validated';
+          const expectedCodeCalls = [
+            expectedCode,
+            expectedRevertCode,
+          ];
           const validator = new CodeValidatorStub();
-          const script = createScriptDataWithCode()
-            .withCode('expected code to be validated')
-            .withRevertCode('expected revert code to be validated');
+          const scriptCodeFactory = createScriptCodeFactoryStub({
+            scriptCode: new ScriptCodeStub()
+              .withExecute(expectedCode)
+              .withRevert(expectedRevertCode),
+          });
           // act
-          new TestBuilder()
-            .withData(script)
+          new TestContext()
+            .withScriptCodeFactory(scriptCodeFactory)
             .withCodeValidator(validator)
             .parseScript();
           // assert
           validator.assertHistory({
-            validatedCodes: [script.code, script.revertCode],
+            validatedCodes: expectedCodeCalls,
             rules: expectedRules,
           });
         });
@@ -175,7 +248,7 @@ describe('ScriptParser', () => {
           const parseContext = new CategoryCollectionParseContextStub()
             .withCompiler(compiler);
           // act
-          new TestBuilder()
+          new TestContext()
             .withData(script)
             .withCodeValidator(validator)
             .withContext(parseContext)
@@ -188,111 +261,250 @@ describe('ScriptParser', () => {
         });
       });
     });
-    describe('invalid script data', () => {
-      describe('validates script data', () => {
+    describe('validation', () => {
+      describe('validates for name', () => {
         // arrange
-        const createTest = (script: ScriptData): ITestScenario => ({
-          act: () => new TestBuilder()
+        const expectedName = 'expected script name to be validated';
+        const script = createScriptDataWithCall()
+          .withName(expectedName);
+        const expectedContext: ScriptNodeErrorContext = {
+          type: NodeDataType.Script,
+          selfNode: script,
+        };
+        itValidatesName((validatorFactory) => {
+          // act
+          new TestContext()
             .withData(script)
-            .parseScript(),
-          expectedContext: {
-            type: NodeType.Script,
-            selfNode: script,
-          },
+            .withValidatorFactory(validatorFactory)
+            .parseScript();
+          // assert
+          return {
+            expectedNameToValidate: expectedName,
+            expectedErrorContext: expectedContext,
+          };
         });
-        // act and assert
-        new NodeValidationTestRunner()
-          .testInvalidNodeName((invalidName) => {
-            return createTest(
-              createScriptDataWithCall().withName(invalidName),
-            );
-          })
-          .testMissingNodeData((node) => {
-            return createTest(node as ScriptData);
-          })
-          .runThrowingCase({
-            name: 'throws when both function call and code are defined',
-            scenario: createTest(
-              createScriptDataWithCall().withCode('code'),
-            ),
-            expectedMessage: 'Both "call" and "code" are defined.',
-          })
-          .runThrowingCase({
-            name: 'throws when both function call and revertCode are defined',
-            scenario: createTest(
-              createScriptDataWithCall().withRevertCode('revert-code'),
-            ),
-            expectedMessage: 'Both "call" and "revertCode" are defined.',
-          })
-          .runThrowingCase({
-            name: 'throws when neither call or revertCode are defined',
-            scenario: createTest(
-              createScriptDataWithoutCallOrCodes(),
-            ),
-            expectedMessage: 'Neither "call" or "code" is defined.',
-          });
       });
-      it(`rethrows exception if ${Script.name} cannot be constructed`, () => {
+      describe('validates for defined data', () => {
         // arrange
-        const expectedError = 'script creation failed';
-        const factoryMock: ScriptFactoryType = () => { throw new Error(expectedError); };
-        const data = createScriptDataWithCode();
-        // act
-        const act = () => new TestBuilder()
-          .withData(data)
-          .withFactory(factoryMock)
-          .parseScript();
-        // expect
-        expectThrowsNodeError({
-          act,
-          expectedContext: {
-            type: NodeType.Script,
-            selfNode: data,
+        const expectedScript = createScriptDataWithCall();
+        const expectedContext: ScriptNodeErrorContext = {
+          type: NodeDataType.Script,
+          selfNode: expectedScript,
+        };
+        itValidatesDefinedData(
+          (validatorFactory) => {
+            // act
+            new TestContext()
+              .withData(expectedScript)
+              .withValidatorFactory(validatorFactory)
+              .parseScript();
+            // assert
+            return {
+              expectedDataToValidate: expectedScript,
+              expectedErrorContext: expectedContext,
+            };
+          },
+        );
+      });
+      describe('validates data', () => {
+        // arrange
+        const testScenarios = generateDataValidationTestScenarios<ScriptData>(
+          {
+            assertErrorMessage: 'Neither "call" or "code" is defined.',
+            expectFail: [{
+              description: 'with no call or code',
+              data: createScriptDataWithoutCallOrCodes(),
+            }],
+            expectPass: [
+              {
+                description: 'with call',
+                data: createScriptDataWithCall(),
+              },
+              {
+                description: 'with code',
+                data: createScriptDataWithCode(),
+              },
+            ],
           },
-        }, expectedError);
+          {
+            assertErrorMessage: 'Both "call" and "revertCode" are defined.',
+            expectFail: [{
+              description: 'with both call and revertCode',
+              data: createScriptDataWithCall()
+                .withRevertCode('revert-code'),
+            }],
+            expectPass: [
+              {
+                description: 'with call, without revertCode',
+                data: createScriptDataWithCall()
+                  .withRevertCode(undefined),
+              },
+              {
+                description: 'with revertCode, without call',
+                data: createScriptDataWithCode()
+                  .withRevertCode('revert code'),
+              },
+            ],
+          },
+          {
+            assertErrorMessage: 'Both "call" and "code" are defined.',
+            expectFail: [{
+              description: 'with both call and code',
+              data: createScriptDataWithCall()
+                .withCode('code'),
+            }],
+            expectPass: [
+              {
+                description: 'with call, without code',
+                data: createScriptDataWithCall()
+                  .withCode(''),
+              },
+              {
+                description: 'with code, without call',
+                data: createScriptDataWithCode()
+                  .withCode('code'),
+              },
+            ],
+          },
+        );
+        testScenarios.forEach(({
+          description, expectedPass, data: scriptData, expectedMessage,
+        }) => {
+          describe(description, () => {
+            itAsserts({
+              expectedConditionResult: expectedPass,
+              test: (validatorFactory) => {
+                const expectedContext: ScriptNodeErrorContext = {
+                  type: NodeDataType.Script,
+                  selfNode: scriptData,
+                };
+                // act
+                new TestContext()
+                  .withData(scriptData)
+                  .withValidatorFactory(validatorFactory)
+                  .parseScript();
+                // assert
+                expectExists(expectedMessage);
+                return {
+                  expectedErrorMessage: expectedMessage,
+                  expectedErrorContext: expectedContext,
+                };
+              },
+            });
+          });
+        });
+      });
+    });
+    describe('rethrows exception if script factory fails', () => {
+      // arrange
+      const givenData = createScriptDataWithCode();
+      const expectedContextMessage = 'Failed to parse script';
+      const expectedError = new Error();
+      const validatorFactory: NodeDataValidatorFactory = () => {
+        const validatorStub = new NodeDataValidatorStub();
+        validatorStub.createContextualErrorMessage = (message) => message;
+        return validatorStub;
+      };
+      // act & assert
+      itThrowsContextualError({
+        throwingAction: (wrapError) => {
+          const factoryMock: ScriptFactory = () => {
+            throw expectedError;
+          };
+          new TestContext()
+            .withScriptFactory(factoryMock)
+            .withErrorWrapper(wrapError)
+            .withValidatorFactory(validatorFactory)
+            .withData(givenData)
+            .parseScript();
+        },
+        expectedWrappedError: expectedError,
+        expectedContextMessage,
       });
     });
   });
 });
 
-class TestBuilder {
+class TestContext {
   private data: ScriptData = createScriptDataWithCode();
 
   private context: ICategoryCollectionParseContext = new CategoryCollectionParseContextStub();
 
-  private parser: IEnumParser<RecommendationLevel> = new EnumParserStub<RecommendationLevel>()
+  private levelParser: IEnumParser<RecommendationLevel> = new EnumParserStub<RecommendationLevel>()
     .setupDefaultValue(RecommendationLevel.Standard);
 
-  private factory?: ScriptFactoryType = undefined;
+  private scriptFactory: ScriptFactory = createScriptFactorySpy().scriptFactorySpy;
 
   private codeValidator: ICodeValidator = new CodeValidatorStub();
 
-  public withCodeValidator(codeValidator: ICodeValidator) {
+  private errorWrapper: ErrorWithContextWrapper = new ErrorWrapperStub().get();
+
+  private validatorFactory: NodeDataValidatorFactory = createNodeDataValidatorFactoryStub;
+
+  private docsParser: DocsParser = () => ['docs'];
+
+  private scriptCodeFactory: ScriptCodeFactory = createScriptCodeFactoryStub({
+    defaultCodePrefix: TestContext.name,
+  });
+
+  public withCodeValidator(codeValidator: ICodeValidator): this {
     this.codeValidator = codeValidator;
     return this;
   }
 
-  public withData(data: ScriptData) {
+  public withData(data: ScriptData): this {
     this.data = data;
     return this;
   }
 
-  public withContext(context: ICategoryCollectionParseContext) {
+  public withContext(context: ICategoryCollectionParseContext): this {
     this.context = context;
     return this;
   }
 
-  public withParser(parser: IEnumParser<RecommendationLevel>) {
-    this.parser = parser;
+  public withParser(parser: IEnumParser<RecommendationLevel>): this {
+    this.levelParser = parser;
+    return this;
+  }
+
+  public withScriptFactory(scriptFactory: ScriptFactory): this {
+    this.scriptFactory = scriptFactory;
+    return this;
+  }
+
+  public withValidatorFactory(validatorFactory: NodeDataValidatorFactory): this {
+    this.validatorFactory = validatorFactory;
+    return this;
+  }
+
+  public withErrorWrapper(errorWrapper: ErrorWithContextWrapper): this {
+    this.errorWrapper = errorWrapper;
+    return this;
+  }
+
+  public withScriptCodeFactory(scriptCodeFactory: ScriptCodeFactory): this {
+    this.scriptCodeFactory = scriptCodeFactory;
     return this;
   }
 
-  public withFactory(factory: ScriptFactoryType) {
-    this.factory = factory;
+  public withDocsParser(docsParser: DocsParser): this {
+    this.docsParser = docsParser;
     return this;
   }
 
   public parseScript(): Script {
-    return parseScript(this.data, this.context, this.parser, this.factory, this.codeValidator);
+    return parseScript(
+      this.data,
+      this.context,
+      {
+        levelParser: this.levelParser,
+        createScript: this.scriptFactory,
+        codeValidator: this.codeValidator,
+        wrapError: this.errorWrapper,
+        createValidator: this.validatorFactory,
+        createCode: this.scriptCodeFactory,
+        parseDocs: this.docsParser,
+      },
+    );
   }
 }
diff --git a/tests/unit/application/Parser/Script/Validation/CodeValidator.spec.ts b/tests/unit/application/Parser/Script/Validation/CodeValidator.spec.ts
index 3b0bac0b8..56764995a 100644
--- a/tests/unit/application/Parser/Script/Validation/CodeValidator.spec.ts
+++ b/tests/unit/application/Parser/Script/Validation/CodeValidator.spec.ts
@@ -2,13 +2,13 @@ import { describe, it, expect } from 'vitest';
 import { CodeValidator } from '@/application/Parser/Script/Validation/CodeValidator';
 import { CodeValidationRuleStub } from '@tests/unit/shared/Stubs/CodeValidationRuleStub';
 import { itEachAbsentCollectionValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
-import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
+import { itIsSingletonFactory } from '@tests/unit/shared/TestCases/SingletonFactoryTests';
 import type { ICodeLine } from '@/application/Parser/Script/Validation/ICodeLine';
 import type { ICodeValidationRule, IInvalidCodeLine } from '@/application/Parser/Script/Validation/ICodeValidationRule';
 
 describe('CodeValidator', () => {
   describe('instance', () => {
-    itIsSingleton({
+    itIsSingletonFactory({
       getter: () => CodeValidator.instance,
       expectedType: CodeValidator,
     });
diff --git a/tests/unit/domain/Category.spec.ts b/tests/unit/domain/Category.spec.ts
index 1c7ecc1eb..ac09d5bca 100644
--- a/tests/unit/domain/Category.spec.ts
+++ b/tests/unit/domain/Category.spec.ts
@@ -3,50 +3,68 @@ import { Category } from '@/domain/Category';
 import { CategoryStub } from '@tests/unit/shared/Stubs/CategoryStub';
 import { ScriptStub } from '@tests/unit/shared/Stubs/ScriptStub';
 import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
+import type { ICategory, IScript } from '@/domain/ICategory';
 
 describe('Category', () => {
   describe('ctor', () => {
-    describe('throws when name is absent', () => {
+    describe('throws error if name is absent', () => {
       itEachAbsentStringValue((absentValue) => {
         // arrange
         const expectedError = 'missing name';
         const name = absentValue;
         // act
-        const construct = () => new Category(5, name, [], [new CategoryStub(5)], []);
+        const construct = () => new CategoryBuilder()
+          .withName(name)
+          .build();
         // assert
         expect(construct).to.throw(expectedError);
       }, { excludeNull: true, excludeUndefined: true });
     });
-    it('throws when has no children', () => {
+    it('throws error if no children are present', () => {
+      // arrange
       const expectedError = 'A category must have at least one sub-category or script';
-      const construct = () => new Category(5, 'category', [], [], []);
+      const scriptChildren: readonly IScript[] = [];
+      const categoryChildren: readonly ICategory[] = [];
+      // act
+      const construct = () => new CategoryBuilder()
+        .withSubcategories(categoryChildren)
+        .withScripts(scriptChildren)
+        .build();
+      // assert
       expect(construct).to.throw(expectedError);
     });
   });
   describe('getAllScriptsRecursively', () => {
-    it('gets child scripts', () => {
+    it('retrieves direct child scripts', () => {
       // arrange
-      const expected = [new ScriptStub('1'), new ScriptStub('2')];
-      const sut = new Category(0, 'category', [], [], expected);
+      const expectedScripts = [new ScriptStub('1'), new ScriptStub('2')];
+      const sut = new CategoryBuilder()
+        .withScripts(expectedScripts)
+        .build();
       // act
       const actual = sut.getAllScriptsRecursively();
       // assert
-      expect(actual).to.have.deep.members(expected);
+      expect(actual).to.have.deep.members(expectedScripts);
     });
-    it('gets child categories', () => {
+    it('retrieves scripts from direct child categories', () => {
       // arrange
       const expectedScriptIds = ['1', '2', '3', '4'];
       const categories = [
         new CategoryStub(31).withScriptIds('1', '2'),
         new CategoryStub(32).withScriptIds('3', '4'),
       ];
-      const sut = new Category(0, 'category', [], categories, []);
+      const sut = new CategoryBuilder()
+        .withScripts([])
+        .withSubcategories(categories)
+        .build();
       // act
-      const actualIds = sut.getAllScriptsRecursively().map((s) => s.id);
+      const actualIds = sut
+        .getAllScriptsRecursively()
+        .map((s) => s.id);
       // assert
       expect(actualIds).to.have.deep.members(expectedScriptIds);
     });
-    it('gets child scripts and categories', () => {
+    it('retrieves scripts from both direct children and child categories', () => {
       // arrange
       const expectedScriptIds = ['1', '2', '3', '4', '5', '6'];
       const categories = [
@@ -54,13 +72,18 @@ describe('Category', () => {
         new CategoryStub(32).withScriptIds('3', '4'),
       ];
       const scripts = [new ScriptStub('5'), new ScriptStub('6')];
-      const sut = new Category(0, 'category', [], categories, scripts);
+      const sut = new CategoryBuilder()
+        .withSubcategories(categories)
+        .withScripts(scripts)
+        .build();
       // act
-      const actualIds = sut.getAllScriptsRecursively().map((s) => s.id);
+      const actualIds = sut
+        .getAllScriptsRecursively()
+        .map((s) => s.id);
       // assert
       expect(actualIds).to.have.deep.members(expectedScriptIds);
     });
-    it('gets child categories recursively', () => {
+    it('retrieves scripts from nested categories recursively', () => {
       // arrange
       const expectedScriptIds = ['1', '2', '3', '4', '5', '6'];
       const categories = [
@@ -83,45 +106,111 @@ describe('Category', () => {
           ),
       ];
       // assert
-      const sut = new Category(0, 'category', [], categories, []);
+      const sut = new CategoryBuilder()
+        .withScripts([])
+        .withSubcategories(categories)
+        .build();
       // act
-      const actualIds = sut.getAllScriptsRecursively().map((s) => s.id);
+      const actualIds = sut
+        .getAllScriptsRecursively()
+        .map((s) => s.id);
       // assert
       expect(actualIds).to.have.deep.members(expectedScriptIds);
     });
   });
   describe('includes', () => {
-    it('return false when does not include', () => {
+    it('returns false for scripts not included', () => {
       // assert
+      const expectedResult = false;
       const script = new ScriptStub('3');
-      const sut = new Category(0, 'category', [], [new CategoryStub(33).withScriptIds('1', '2')], []);
+      const childCategory = new CategoryStub(33)
+        .withScriptIds('1', '2');
+      const sut = new CategoryBuilder()
+        .withSubcategories([childCategory])
+        .build();
       // act
       const actual = sut.includes(script);
       // assert
-      expect(actual).to.equal(false);
+      expect(actual).to.equal(expectedResult);
     });
-    it('return true when includes as subscript', () => {
+    it('returns true for scripts directly included', () => {
       // assert
+      const expectedResult = true;
       const script = new ScriptStub('3');
-      const sut = new Category(0, 'category', [], [
-        new CategoryStub(33).withScript(script).withScriptIds('non-related'),
-      ], []);
+      const childCategory = new CategoryStub(33)
+        .withScript(script)
+        .withScriptIds('non-related');
+      const sut = new CategoryBuilder()
+        .withSubcategories([childCategory])
+        .build();
       // act
       const actual = sut.includes(script);
       // assert
-      expect(actual).to.equal(true);
+      expect(actual).to.equal(expectedResult);
     });
-    it('return true when includes as nested category script', () => {
+    it('returns true for scripts included in nested categories', () => {
       // assert
+      const expectedResult = true;
       const script = new ScriptStub('3');
-      const innerCategory = new CategoryStub(22)
+      const childCategory = new CategoryStub(22)
         .withScriptIds('non-related')
         .withCategory(new CategoryStub(33).withScript(script));
-      const sut = new Category(11, 'category', [], [innerCategory], []);
+      const sut = new CategoryBuilder()
+        .withSubcategories([childCategory])
+        .build();
       // act
       const actual = sut.includes(script);
       // assert
-      expect(actual).to.equal(true);
+      expect(actual).to.equal(expectedResult);
     });
   });
 });
+
+class CategoryBuilder {
+  private id = 3264;
+
+  private name = 'test-script';
+
+  private docs: ReadonlyArray<string> = [];
+
+  private subcategories: ReadonlyArray<ICategory> = [];
+
+  private scripts: ReadonlyArray<IScript> = [
+    new ScriptStub(`[${CategoryBuilder.name}] script`),
+  ];
+
+  public withId(id: number): this {
+    this.id = id;
+    return this;
+  }
+
+  public withName(name: string): this {
+    this.name = name;
+    return this;
+  }
+
+  public withDocs(docs: ReadonlyArray<string>): this {
+    this.docs = docs;
+    return this;
+  }
+
+  public withScripts(scripts: ReadonlyArray<IScript>): this {
+    this.scripts = scripts;
+    return this;
+  }
+
+  public withSubcategories(subcategories: ReadonlyArray<ICategory>): this {
+    this.subcategories = subcategories;
+    return this;
+  }
+
+  public build(): Category {
+    return new Category({
+      id: this.id,
+      name: this.name,
+      docs: this.docs,
+      subcategories: this.subcategories,
+      scripts: this.scripts,
+    });
+  }
+}
diff --git a/tests/unit/domain/Script.spec.ts b/tests/unit/domain/Script.spec.ts
index f27d11563..0ae0c049b 100644
--- a/tests/unit/domain/Script.spec.ts
+++ b/tests/unit/domain/Script.spec.ts
@@ -8,7 +8,7 @@ import { ScriptCodeStub } from '@tests/unit/shared/Stubs/ScriptCodeStub';
 describe('Script', () => {
   describe('ctor', () => {
     describe('scriptCode', () => {
-      it('sets as expected', () => {
+      it('assigns code correctly', () => {
         // arrange
         const expected = new ScriptCodeStub();
         const sut = new ScriptBuilder()
@@ -43,7 +43,7 @@ describe('Script', () => {
       });
     });
     describe('level', () => {
-      it('cannot construct with invalid wrong value', () => {
+      it('throws when constructed with invalid level', () => {
         // arrange
         const invalidValue: RecommendationLevel = 55 as never;
         const expectedError = 'invalid level';
@@ -54,7 +54,7 @@ describe('Script', () => {
         // assert
         expect(construct).to.throw(expectedError);
       });
-      it('sets undefined as expected', () => {
+      it('handles undefined level correctly', () => {
         // arrange
         const expected = undefined;
         // act
@@ -64,7 +64,7 @@ describe('Script', () => {
         // assert
         expect(sut.level).to.equal(expected);
       });
-      it('sets as expected', () => {
+      it('correctly assigns valid recommendation levels', () => {
         // arrange
         for (const expected of getEnumValues(RecommendationLevel)) {
           // act
@@ -78,7 +78,7 @@ describe('Script', () => {
       });
     });
     describe('docs', () => {
-      it('sets as expected', () => {
+      it('correctly assigns docs', () => {
         // arrange
         const expected = ['doc1', 'doc2'];
         // act
@@ -130,11 +130,11 @@ class ScriptBuilder {
   }
 
   public build(): Script {
-    return new Script(
-      this.name,
-      this.code,
-      this.docs,
-      this.level,
-    );
+    return new Script({
+      name: this.name,
+      code: this.code,
+      docs: this.docs,
+      level: this.level,
+    });
   }
 }
diff --git a/tests/unit/domain/ScriptCodeFactory.spec.ts b/tests/unit/domain/ScriptCodeFactory.spec.ts
new file mode 100644
index 000000000..feb1e54bc
--- /dev/null
+++ b/tests/unit/domain/ScriptCodeFactory.spec.ts
@@ -0,0 +1,52 @@
+import { createScriptCode } from '@/domain/ScriptCodeFactory';
+
+describe('ScriptCodeFactory', () => {
+  describe('createScriptCode', () => {
+    it('generates script code with given `code`', () => {
+      // arrange
+      const expectedCode = 'expected code';
+      const context = new TestContext()
+        .withCode(expectedCode);
+      // act
+      const code = context.createScriptCode();
+      // assert
+      const actualCode = code.execute;
+      expect(actualCode).to.equal(expectedCode);
+    });
+
+    it('generates script code with given `revertCode`', () => {
+      // arrange
+      const expectedRevertCode = 'expected revert code';
+      const context = new TestContext()
+        .withRevertCode(expectedRevertCode);
+      // act
+      const code = context.createScriptCode();
+      // assert
+      const actualRevertCode = code.revert;
+      expect(actualRevertCode).to.equal(expectedRevertCode);
+    });
+  });
+});
+
+class TestContext {
+  private code = `[${TestContext}] code`;
+
+  private revertCode = `[${TestContext}] revertCode`;
+
+  public withCode(code: string): this {
+    this.code = code;
+    return this;
+  }
+
+  public withRevertCode(revertCode: string): this {
+    this.revertCode = revertCode;
+    return this;
+  }
+
+  public createScriptCode(): ReturnType<typeof createScriptCode> {
+    return createScriptCode(
+      this.code,
+      this.revertCode,
+    );
+  }
+}
diff --git a/tests/unit/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory.ts b/tests/unit/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory.ts
index 6f8976622..bd443a20c 100644
--- a/tests/unit/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory.ts
+++ b/tests/unit/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory.ts
@@ -1,7 +1,7 @@
 import {
   describe,
 } from 'vitest';
-import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
+import { itIsSingletonFactory } from '@tests/unit/shared/TestCases/SingletonFactoryTests';
 import { EnvironmentVariablesFactory, type EnvironmentVariablesValidator } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
 import { ViteEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/Vite/ViteEnvironmentVariables';
 import type { IEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/IEnvironmentVariables';
@@ -9,7 +9,7 @@ import { expectExists } from '@tests/shared/Assertions/ExpectExists';
 
 describe('EnvironmentVariablesFactory', () => {
   describe('instance', () => {
-    itIsSingleton({
+    itIsSingletonFactory({
       getter: () => EnvironmentVariablesFactory.Current.instance,
       expectedType: ViteEnvironmentVariables,
     });
diff --git a/tests/unit/presentation/bootstrapping/DependencyProvider.spec.ts b/tests/unit/presentation/bootstrapping/DependencyProvider.spec.ts
index d9f16e627..767594aaa 100644
--- a/tests/unit/presentation/bootstrapping/DependencyProvider.spec.ts
+++ b/tests/unit/presentation/bootstrapping/DependencyProvider.spec.ts
@@ -3,8 +3,9 @@ import { VueDependencyInjectionApiStub } from '@tests/unit/shared/Stubs/VueDepen
 import { InjectionKeys } from '@/presentation/injectionSymbols';
 import { provideDependencies, type VueDependencyInjectionApi } from '@/presentation/bootstrapping/DependencyProvider';
 import { ApplicationContextStub } from '@tests/unit/shared/Stubs/ApplicationContextStub';
-import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
+import { itIsSingletonFactory } from '@tests/unit/shared/TestCases/SingletonFactoryTests';
 import type { IApplicationContext } from '@/application/Context/IApplicationContext';
+import { itIsTransientFactory } from '@tests/unit/shared/TestCases/TransientFactoryTests';
 
 describe('DependencyProvider', () => {
   describe('provideDependencies', () => {
@@ -46,19 +47,22 @@ function createTransientTests() {
       const registeredObject = api.inject(injectionKey);
       expect(registeredObject).to.be.instanceOf(Function);
     });
-    it('should return different instances for transient dependency', () => {
+    describe('should return different instances for transient dependency', () => {
       // arrange
       const api = new VueDependencyInjectionApiStub();
-      // act
       new ProvideDependenciesBuilder()
         .withApi(api)
         .provideDependencies();
-      // expect
-      const registeredObject = api.inject(injectionKey);
-      const factory = registeredObject as () => unknown;
-      const firstResult = factory();
-      const secondResult = factory();
-      expect(firstResult).to.not.equal(secondResult);
+      // act
+      const getFactoryResult = () => {
+        const registeredObject = api.inject(injectionKey);
+        const factory = registeredObject as () => unknown;
+        return factory();
+      };
+      // assert
+      itIsTransientFactory({
+        getter: getFactoryResult,
+      });
     });
   };
 }
@@ -87,7 +91,7 @@ function createSingletonTests() {
       // act
       const getRegisteredInstance = () => api.inject(injectionKey);
       // assert
-      itIsSingleton({
+      itIsSingletonFactory({
         getter: getRegisteredInstance,
       });
     });
diff --git a/tests/unit/presentation/components/Scripts/View/Tree/NodeContent/Reverter/ReverterFactory.spec.ts b/tests/unit/presentation/components/Scripts/View/Tree/NodeContent/Reverter/ReverterFactory.spec.ts
index 5e99c92ed..aee90dbc5 100644
--- a/tests/unit/presentation/components/Scripts/View/Tree/NodeContent/Reverter/ReverterFactory.spec.ts
+++ b/tests/unit/presentation/components/Scripts/View/Tree/NodeContent/Reverter/ReverterFactory.spec.ts
@@ -6,8 +6,7 @@ import { CategoryCollectionStub } from '@tests/unit/shared/Stubs/CategoryCollect
 import { CategoryStub } from '@tests/unit/shared/Stubs/CategoryStub';
 import { ScriptStub } from '@tests/unit/shared/Stubs/ScriptStub';
 import { getCategoryNodeId, getScriptNodeId } from '@/presentation/components/Scripts/View/Tree/TreeViewAdapter/CategoryNodeMetadataConverter';
-import { NodeType } from '@/application/Parser/NodeValidation/NodeType';
-import type { NodeMetadata } from '@/presentation/components/Scripts/View/Tree/NodeContent/NodeMetadata';
+import { type NodeMetadata, NodeType } from '@/presentation/components/Scripts/View/Tree/NodeContent/NodeMetadata';
 
 describe('ReverterFactory', () => {
   describe('getReverter', () => {
diff --git a/tests/unit/presentation/components/Scripts/View/Tree/TreeViewAdapter/CategoryNodeMetadataConverter.spec.ts b/tests/unit/presentation/components/Scripts/View/Tree/TreeViewAdapter/CategoryNodeMetadataConverter.spec.ts
index b26925666..1ea61c427 100644
--- a/tests/unit/presentation/components/Scripts/View/Tree/TreeViewAdapter/CategoryNodeMetadataConverter.spec.ts
+++ b/tests/unit/presentation/components/Scripts/View/Tree/TreeViewAdapter/CategoryNodeMetadataConverter.spec.ts
@@ -8,7 +8,7 @@ import {
   getCategoryId, getCategoryNodeId, getScriptId,
   getScriptNodeId, parseAllCategories, parseSingleCategory,
 } from '@/presentation/components/Scripts/View/Tree/TreeViewAdapter/CategoryNodeMetadataConverter';
-import { NodeType } from '@/application/Parser/NodeValidation/NodeType';
+import { NodeDataType } from '@/application/Parser/NodeValidation/NodeDataType';
 import type { NodeMetadata } from '@/presentation/components/Scripts/View/Tree/NodeContent/NodeMetadata';
 import { expectExists } from '@tests/shared/Assertions/ExpectExists';
 
@@ -109,7 +109,7 @@ function isReversible(category: ICategory): boolean {
 }
 
 function expectSameCategory(node: NodeMetadata, category: ICategory): void {
-  expect(node.type).to.equal(NodeType.Category, getErrorMessage('type'));
+  expect(node.type).to.equal(NodeDataType.Category, getErrorMessage('type'));
   expect(node.id).to.equal(getCategoryNodeId(category), getErrorMessage('id'));
   expect(node.docs).to.equal(category.docs, getErrorMessage('docs'));
   expect(node.text).to.equal(category.name, getErrorMessage('name'));
@@ -136,7 +136,7 @@ function expectSameCategory(node: NodeMetadata, category: ICategory): void {
 }
 
 function expectSameScript(node: NodeMetadata, script: IScript): void {
-  expect(node.type).to.equal(NodeType.Script, getErrorMessage('type'));
+  expect(node.type).to.equal(NodeDataType.Script, getErrorMessage('type'));
   expect(node.id).to.equal(getScriptNodeId(script), getErrorMessage('id'));
   expect(node.docs).to.equal(script.docs, getErrorMessage('docs'));
   expect(node.text).to.equal(script.name, getErrorMessage('name'));
diff --git a/tests/unit/presentation/components/Shared/Hooks/Log/ClientLoggerFactory.spec.ts b/tests/unit/presentation/components/Shared/Hooks/Log/ClientLoggerFactory.spec.ts
index 73be59138..2a15a9eae 100644
--- a/tests/unit/presentation/components/Shared/Hooks/Log/ClientLoggerFactory.spec.ts
+++ b/tests/unit/presentation/components/Shared/Hooks/Log/ClientLoggerFactory.spec.ts
@@ -3,14 +3,14 @@ import { describe, it } from 'vitest';
 import type { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
 import type { Logger } from '@/application/Common/Log/Logger';
 import { RuntimeEnvironmentStub } from '@tests/unit/shared/Stubs/RuntimeEnvironmentStub';
-import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
+import { itIsSingletonFactory } from '@tests/unit/shared/TestCases/SingletonFactoryTests';
 import { LoggerStub } from '@tests/unit/shared/Stubs/LoggerStub';
 import { ClientLoggerFactory, type LoggerCreationFunction, type WindowAccessor } from '@/presentation/components/Shared/Hooks/Log/ClientLoggerFactory';
 
 describe('ClientLoggerFactory', () => {
   describe('Current', () => {
     describe('singleton behavior', () => {
-      itIsSingleton({
+      itIsSingletonFactory({
         getter: () => ClientLoggerFactory.Current,
         expectedType: ClientLoggerFactory,
       });
diff --git a/tests/unit/shared/ExceptionCollector.ts b/tests/unit/shared/ExceptionCollector.ts
index ec791c091..f25feba00 100644
--- a/tests/unit/shared/ExceptionCollector.ts
+++ b/tests/unit/shared/ExceptionCollector.ts
@@ -1,12 +1,18 @@
 export function collectExceptionMessage(action: () => unknown): string {
-  let message: string | undefined;
+  return collectException(action).message;
+}
+
+function collectException(
+  action: () => unknown,
+): Error {
+  let error: Error | undefined;
   try {
     action();
-  } catch (e) {
-    message = e.message;
+  } catch (err) {
+    error = err;
   }
-  if (!message) {
+  if (!error) {
     throw new Error('action did not throw');
   }
-  return message;
+  return error;
 }
diff --git a/tests/unit/shared/Stubs/CategoryFactoryStub.ts b/tests/unit/shared/Stubs/CategoryFactoryStub.ts
new file mode 100644
index 000000000..2fb0d8782
--- /dev/null
+++ b/tests/unit/shared/Stubs/CategoryFactoryStub.ts
@@ -0,0 +1,19 @@
+import type { CategoryFactory } from '@/application/Parser/CategoryParser';
+import type { CategoryInitParameters } from '@/domain/Category';
+import type { ICategory } from '@/domain/ICategory';
+import { CategoryStub } from './CategoryStub';
+
+export function createCategoryFactorySpy(): {
+  readonly categoryFactorySpy: CategoryFactory;
+  getInitParameters: (category: ICategory) => CategoryInitParameters | undefined;
+} {
+  const createdCategories = new Map<ICategory, CategoryInitParameters>();
+  return {
+    categoryFactorySpy: (parameters) => {
+      const category = new CategoryStub(55);
+      createdCategories.set(category, parameters);
+      return category;
+    },
+    getInitParameters: (category) => createdCategories.get(category),
+  };
+}
diff --git a/tests/unit/shared/Stubs/CodeValidatorStub.ts b/tests/unit/shared/Stubs/CodeValidatorStub.ts
index 553677a9b..0df804777 100644
--- a/tests/unit/shared/Stubs/CodeValidatorStub.ts
+++ b/tests/unit/shared/Stubs/CodeValidatorStub.ts
@@ -19,16 +19,16 @@ export class CodeValidatorStub implements ICodeValidator {
     });
   }
 
-  public assertHistory(expected: {
+  public assertHistory(expectation: {
     validatedCodes: readonly (string | undefined)[],
     rules: readonly Constructible<ICodeValidationRule>[],
   }) {
-    expect(this.callHistory).to.have.lengthOf(expected.validatedCodes.length);
+    expect(this.callHistory).to.have.lengthOf(expectation.validatedCodes.length);
     const actualValidatedCodes = this.callHistory.map((args) => args.code);
-    expect(actualValidatedCodes.sort()).deep.equal([...expected.validatedCodes].sort());
+    expect(actualValidatedCodes.sort()).deep.equal([...expectation.validatedCodes].sort());
     for (const call of this.callHistory) {
       const actualRules = call.rules.map((rule) => rule.constructor);
-      expect(actualRules.sort()).to.deep.equal([...expected.rules].sort());
+      expect(actualRules.sort()).to.deep.equal([...expectation.rules].sort());
     }
   }
 }
diff --git a/tests/unit/shared/Stubs/ErrorWithContextWrapperStub.ts b/tests/unit/shared/Stubs/ErrorWithContextWrapperStub.ts
new file mode 100644
index 000000000..10aadeae3
--- /dev/null
+++ b/tests/unit/shared/Stubs/ErrorWithContextWrapperStub.ts
@@ -0,0 +1,4 @@
+import type { ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
+
+export const errorWithContextWrapperStub
+: ErrorWithContextWrapper = (error, message) => new Error(`[stubbed error wrapper] ${error.message} + ${message}`);
diff --git a/tests/unit/shared/Stubs/ErrorWrapperStub.ts b/tests/unit/shared/Stubs/ErrorWrapperStub.ts
new file mode 100644
index 000000000..080d5b376
--- /dev/null
+++ b/tests/unit/shared/Stubs/ErrorWrapperStub.ts
@@ -0,0 +1,67 @@
+import type { ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
+
+export class ErrorWrapperStub {
+  private errorToReturn: Error | undefined;
+
+  private parameters?: Parameters<ErrorWithContextWrapper>;
+
+  public get lastError(): Error | undefined {
+    if (!this.parameters) {
+      return undefined;
+    }
+    return getError(this.parameters);
+  }
+
+  public get lastContext(): string | undefined {
+    if (!this.parameters) {
+      return undefined;
+    }
+    return getAdditionalContext(this.parameters);
+  }
+
+  public withError(error: Error): this {
+    this.errorToReturn = error;
+    return this;
+  }
+
+  public get(): ErrorWithContextWrapper {
+    return (...args) => {
+      this.parameters = args;
+      if (this.errorToReturn) {
+        return this.errorToReturn;
+      }
+      return new Error(
+        `[${ErrorWrapperStub.name}] Error wrapped with additional context.`
+        + `\nAdditional context: ${getAdditionalContext(args)}`
+        + `\nWrapped error message: ${getError(args).message}`
+        + `\nWrapped error stack trace:\n${getLimitedStackTrace(getError(args), 5)}`,
+      );
+    };
+  }
+}
+
+function getAdditionalContext(
+  parameters: Parameters<ErrorWithContextWrapper>,
+): string {
+  return parameters[1];
+}
+
+function getError(
+  parameters: Parameters<ErrorWithContextWrapper>,
+): Error {
+  return parameters[0];
+}
+
+function getLimitedStackTrace(
+  error: Error,
+  limit: number,
+): string {
+  const { stack } = error;
+  if (!stack) {
+    return 'No stack trace available';
+  }
+  return stack
+    .split('\n')
+    .slice(0, limit + 1)
+    .join('\n');
+}
diff --git a/tests/unit/shared/Stubs/FunctionCallCompilerStub.ts b/tests/unit/shared/Stubs/FunctionCallCompilerStub.ts
index 03a0a3d8c..4c21ca187 100644
--- a/tests/unit/shared/Stubs/FunctionCallCompilerStub.ts
+++ b/tests/unit/shared/Stubs/FunctionCallCompilerStub.ts
@@ -2,22 +2,33 @@ import type { CompiledCode } from '@/application/Parser/Script/Compiler/Function
 import type { FunctionCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler';
 import type { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
 import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
+import { CompiledCodeStub } from './CompiledCodeStub';
 
-interface IScenario {
-  calls: FunctionCall[];
-  functions: ISharedFunctionCollection;
-  result: CompiledCode;
+interface FunctionCallCompilationTestScenario {
+  readonly calls: FunctionCall[];
+  readonly functions: ISharedFunctionCollection;
+  readonly result: CompiledCode;
 }
 
 export class FunctionCallCompilerStub implements FunctionCallCompiler {
-  public scenarios = new Array<IScenario>();
+  public scenarios = new Array<FunctionCallCompilationTestScenario>();
+
+  private defaultCompiledCode: CompiledCode = new CompiledCodeStub()
+    .withCode(`[${FunctionCallCompilerStub.name}] function code`)
+    .withRevertCode(`[${FunctionCallCompilerStub.name}] function revert code`);
 
   public setup(
     calls: FunctionCall[],
     functions: ISharedFunctionCollection,
     result: CompiledCode,
-  ) {
+  ): this {
     this.scenarios.push({ calls, functions, result });
+    return this;
+  }
+
+  public withDefaultCompiledCode(defaultCompiledCode: CompiledCode): this {
+    this.defaultCompiledCode = defaultCompiledCode;
+    return this;
   }
 
   public compileFunctionCalls(
@@ -29,10 +40,7 @@ export class FunctionCallCompilerStub implements FunctionCallCompiler {
     if (predefined) {
       return predefined.result;
     }
-    return {
-      code: 'function code [FunctionCallCompilerStub]',
-      revertCode: 'function revert code [FunctionCallCompilerStub]',
-    };
+    return this.defaultCompiledCode;
   }
 }
 
diff --git a/tests/unit/shared/Stubs/FunctionParameterCollectionStub.ts b/tests/unit/shared/Stubs/FunctionParameterCollectionStub.ts
index db3575e49..e4eb62fe5 100644
--- a/tests/unit/shared/Stubs/FunctionParameterCollectionStub.ts
+++ b/tests/unit/shared/Stubs/FunctionParameterCollectionStub.ts
@@ -16,7 +16,7 @@ export class FunctionParameterCollectionStub implements IFunctionParameterCollec
   public withParameterName(parameterName: string, isOptional = true) {
     const parameter = new FunctionParameterStub()
       .withName(parameterName)
-      .withOptionality(isOptional);
+      .withOptional(isOptional);
     this.addParameter(parameter);
     return this;
   }
diff --git a/tests/unit/shared/Stubs/FunctionParameterStub.ts b/tests/unit/shared/Stubs/FunctionParameterStub.ts
index 5f65c17d8..2ddb7df16 100644
--- a/tests/unit/shared/Stubs/FunctionParameterStub.ts
+++ b/tests/unit/shared/Stubs/FunctionParameterStub.ts
@@ -10,7 +10,7 @@ export class FunctionParameterStub implements IFunctionParameter {
     return this;
   }
 
-  public withOptionality(isOptional: boolean) {
+  public withOptional(isOptional: boolean) {
     this.isOptional = isOptional;
     return this;
   }
diff --git a/tests/unit/shared/Stubs/NodeDataErrorContextStub.ts b/tests/unit/shared/Stubs/NodeDataErrorContextStub.ts
index be2ea6763..eb49c784c 100644
--- a/tests/unit/shared/Stubs/NodeDataErrorContextStub.ts
+++ b/tests/unit/shared/Stubs/NodeDataErrorContextStub.ts
@@ -1,12 +1,11 @@
-import type { NodeData } from '@/application/Parser/NodeValidation/NodeData';
-import type { INodeDataErrorContext } from '@/application/Parser/NodeValidation/NodeDataError';
-import { NodeType } from '@/application/Parser/NodeValidation/NodeType';
+import type { NodeDataErrorContext } from '@/application/Parser/NodeValidation/NodeDataErrorContext';
+import { NodeDataType } from '@/application/Parser/NodeValidation/NodeDataType';
 import { CategoryDataStub } from './CategoryDataStub';
 
-export class NodeDataErrorContextStub implements INodeDataErrorContext {
-  public readonly type: NodeType = NodeType.Script;
-
-  public readonly selfNode: NodeData = new CategoryDataStub();
-
-  public readonly parentNode?: NodeData;
+export function createNodeDataErrorContextStub(): NodeDataErrorContext {
+  return {
+    type: NodeDataType.Category,
+    selfNode: new CategoryDataStub(),
+    parentNode: new CategoryDataStub(),
+  };
 }
diff --git a/tests/unit/shared/Stubs/NodeDataValidatorStub.ts b/tests/unit/shared/Stubs/NodeDataValidatorStub.ts
new file mode 100644
index 000000000..983b8e147
--- /dev/null
+++ b/tests/unit/shared/Stubs/NodeDataValidatorStub.ts
@@ -0,0 +1,57 @@
+import type { NodeData } from '@/application/Parser/NodeValidation/NodeData';
+import type { NodeDataValidator, NodeDataValidatorFactory } from '@/application/Parser/NodeValidation/NodeDataValidator';
+import { StubWithObservableMethodCalls } from './StubWithObservableMethodCalls';
+
+export const createNodeDataValidatorFactoryStub
+: NodeDataValidatorFactory = () => new NodeDataValidatorStub();
+
+export class NodeDataValidatorStub
+  extends StubWithObservableMethodCalls<NodeDataValidator>
+  implements NodeDataValidator {
+  private assertThrowsOnFalseCondition = true;
+
+  public withAssertThrowsOnFalseCondition(enableAssertThrows: boolean): this {
+    this.assertThrowsOnFalseCondition = enableAssertThrows;
+    return this;
+  }
+
+  public assertValidName(nameValue: string): this {
+    this.registerMethodCall({
+      methodName: 'assertValidName',
+      args: [nameValue],
+    });
+    return this;
+  }
+
+  public assertDefined(node: NodeData): this {
+    this.registerMethodCall({
+      methodName: 'assertDefined',
+      args: [node],
+    });
+    return this;
+  }
+
+  public assert(
+    validationPredicate: () => boolean,
+    errorMessage: string,
+  ): this {
+    this.registerMethodCall({
+      methodName: 'assert',
+      args: [validationPredicate, errorMessage],
+    });
+    if (this.assertThrowsOnFalseCondition) {
+      if (!validationPredicate()) {
+        throw new Error(`[${NodeDataValidatorStub.name}] Assert validation failed: ${errorMessage}`);
+      }
+    }
+    return this;
+  }
+
+  public createContextualErrorMessage(errorMessage: string): string {
+    this.registerMethodCall({
+      methodName: 'createContextualErrorMessage',
+      args: [errorMessage],
+    });
+    return `${NodeDataValidatorStub.name}: ${errorMessage}`;
+  }
+}
diff --git a/tests/unit/shared/Stubs/ScriptCodeFactoryStub.ts b/tests/unit/shared/Stubs/ScriptCodeFactoryStub.ts
new file mode 100644
index 000000000..9675740e0
--- /dev/null
+++ b/tests/unit/shared/Stubs/ScriptCodeFactoryStub.ts
@@ -0,0 +1,22 @@
+import type { ScriptCodeFactory } from '@/domain/ScriptCodeFactory';
+import type { IScriptCode } from '@/domain/IScriptCode';
+import { ScriptCodeStub } from './ScriptCodeStub';
+
+export function createScriptCodeFactoryStub(
+  options?: Partial<StubOptions>,
+): ScriptCodeFactory {
+  let defaultCodePrefix = 'createScriptCodeFactoryStub';
+  if (options?.defaultCodePrefix) {
+    defaultCodePrefix += ` > ${options?.defaultCodePrefix}`;
+  }
+  return (
+    () => options?.scriptCode ?? new ScriptCodeStub()
+      .withExecute(`[${defaultCodePrefix}] default code`)
+      .withRevert(`[${defaultCodePrefix}] revert code`)
+  );
+}
+
+interface StubOptions {
+  readonly scriptCode?: IScriptCode;
+  readonly defaultCodePrefix?: string;
+}
diff --git a/tests/unit/shared/Stubs/ScriptCodeStub.ts b/tests/unit/shared/Stubs/ScriptCodeStub.ts
index dc9f879f4..d77a7413c 100644
--- a/tests/unit/shared/Stubs/ScriptCodeStub.ts
+++ b/tests/unit/shared/Stubs/ScriptCodeStub.ts
@@ -1,9 +1,9 @@
 import type { IScriptCode } from '@/domain/IScriptCode';
 
 export class ScriptCodeStub implements IScriptCode {
-  public execute = 'default execute code';
+  public execute = `[${ScriptCodeStub.name}] default execute code`;
 
-  public revert = 'default revert code';
+  public revert = `[${ScriptCodeStub.name}] default revert code`;
 
   public withExecute(code: string) {
     this.execute = code;
diff --git a/tests/unit/shared/Stubs/ScriptFactoryStub.ts b/tests/unit/shared/Stubs/ScriptFactoryStub.ts
new file mode 100644
index 000000000..b3a28f62b
--- /dev/null
+++ b/tests/unit/shared/Stubs/ScriptFactoryStub.ts
@@ -0,0 +1,19 @@
+import type { ScriptFactory } from '@/application/Parser/Script/ScriptParser';
+import type { IScript } from '@/domain/IScript';
+import type { ScriptInitParameters } from '@/domain/Script';
+import { ScriptStub } from './ScriptStub';
+
+export function createScriptFactorySpy(): {
+  readonly scriptFactorySpy: ScriptFactory;
+  getInitParameters: (category: IScript) => ScriptInitParameters | undefined;
+} {
+  const createdScripts = new Map<IScript, ScriptInitParameters>();
+  return {
+    scriptFactorySpy: (parameters) => {
+      const script = new ScriptStub('script from factory stub');
+      createdScripts.set(script, parameters);
+      return script;
+    },
+    getInitParameters: (script) => createdScripts.get(script),
+  };
+}
diff --git a/tests/unit/shared/Stubs/ScriptParserStub.ts b/tests/unit/shared/Stubs/ScriptParserStub.ts
new file mode 100644
index 000000000..217dbd211
--- /dev/null
+++ b/tests/unit/shared/Stubs/ScriptParserStub.ts
@@ -0,0 +1,37 @@
+import type { ScriptParser } from '@/application/Parser/Script/ScriptParser';
+import type { IScript } from '@/domain/IScript';
+import type { ScriptData } from '@/application/collections/';
+import { ScriptStub } from './ScriptStub';
+
+export class ScriptParserStub {
+  private readonly parsedScripts = new Map<IScript, Parameters<ScriptParser>>();
+
+  private readonly setupScripts = new Map<ScriptData, IScript>();
+
+  public get(): ScriptParser {
+    return (...parameters) => {
+      const [scriptData] = parameters;
+      const script = this.setupScripts.get(scriptData)
+        ?? new ScriptStub(
+          `[${ScriptParserStub.name}] parsed script stub number ${this.parsedScripts.size + 1}`,
+        );
+      this.parsedScripts.set(script, parameters);
+      return script;
+    };
+  }
+
+  public getParseParameters(
+    script: IScript,
+  ): Parameters<ScriptParser> {
+    const parameters = this.parsedScripts.get(script);
+    if (!parameters) {
+      throw new Error('Script has never been parsed.');
+    }
+    return parameters;
+  }
+
+  public setupParsedResultForData(scriptData: ScriptData, parsedResult: IScript): this {
+    this.setupScripts.set(scriptData, parsedResult);
+    return this;
+  }
+}
diff --git a/tests/unit/shared/Stubs/StubWithObservableMethodCalls.ts b/tests/unit/shared/Stubs/StubWithObservableMethodCalls.ts
index e008277c4..5d8f0d8fb 100644
--- a/tests/unit/shared/Stubs/StubWithObservableMethodCalls.ts
+++ b/tests/unit/shared/Stubs/StubWithObservableMethodCalls.ts
@@ -5,12 +5,12 @@ import type { FunctionKeys } from '@/TypeHelpers';
 export abstract class StubWithObservableMethodCalls<T> {
   public readonly callHistory = new Array<MethodCall<T>>();
 
+  private readonly notifiableMethodCalls = new EventSource<MethodCall<T>>();
+
   public get methodCalls(): IEventSource<MethodCall<T>> {
     return this.notifiableMethodCalls;
   }
 
-  private readonly notifiableMethodCalls = new EventSource<MethodCall<T>>();
-
   protected registerMethodCall(name: MethodCall<T>) {
     this.callHistory.push(name);
     this.notifiableMethodCalls.notify(name);
diff --git a/tests/unit/shared/TestCases/SingletonTests.ts b/tests/unit/shared/TestCases/SingletonFactoryTests.ts
similarity index 84%
rename from tests/unit/shared/TestCases/SingletonTests.ts
rename to tests/unit/shared/TestCases/SingletonFactoryTests.ts
index 5bab0586d..52779a458 100644
--- a/tests/unit/shared/TestCases/SingletonTests.ts
+++ b/tests/unit/shared/TestCases/SingletonFactoryTests.ts
@@ -1,12 +1,12 @@
 import { it, expect } from 'vitest';
 import type { Constructible } from '@/TypeHelpers';
 
-interface ISingletonTestData<T> {
+interface SingletonTestData<T> {
   readonly getter: () => T;
   readonly expectedType?: Constructible<T>;
 }
 
-export function itIsSingleton<T>(test: ISingletonTestData<T>): void {
+export function itIsSingletonFactory<T>(test: SingletonTestData<T>): void {
   if (test.expectedType !== undefined) {
     it('gets the expected type', () => {
       // act
diff --git a/tests/unit/shared/TestCases/TransientFactoryTests.ts b/tests/unit/shared/TestCases/TransientFactoryTests.ts
new file mode 100644
index 000000000..afdfb7b35
--- /dev/null
+++ b/tests/unit/shared/TestCases/TransientFactoryTests.ts
@@ -0,0 +1,24 @@
+import type { Constructible } from '@/TypeHelpers';
+
+interface TransientFactoryTestData<T> {
+  readonly getter: () => T;
+  readonly expectedType?: Constructible<T>;
+}
+
+export function itIsTransientFactory<T>(test: TransientFactoryTestData<T>): void {
+  if (test.expectedType !== undefined) {
+    it('gets the expected type', () => {
+      // act
+      const instance = test.getter();
+      // assert
+      expect(instance).to.be.instanceOf(test.expectedType);
+    });
+  }
+  it('multiple calls get different instances', () => {
+    // act
+    const instance1 = test.getter();
+    const instance2 = test.getter();
+    // assert
+    expect(instance1).to.not.equal(instance2);
+  });
+}