You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi DUnitX项目Mock ShowMessage遇E2250错误的解决方法

问题修复方案:Delphi DUnitX中Mock ShowMessage与MessageDlg

错误原因

你遇到的编译错误E2250,是因为**Vcl.Dialogs.ShowMessage是全局过程,并非可直接赋值的变量/函数指针**,原代码中直接赋值的写法不符合Delphi语法规则。以下提供两种无需修改被测试单元的解决方案:


方案1:单元重定向(优先推荐,无运行时Hook风险)

通过创建Mock单元覆盖官方Vcl.Dialogs的实现,利用Delphi编译路径优先级让被测试单元优先使用Mock版本。

步骤1:创建MockDialogs单元

unit MockDialogs;

interface

uses
  Vcl.Dialogs, System.SysUtils, Winapi.Messages, Vcl.Forms;

// 全局Mock配置与捕获数据
var
  AutoProceed: Boolean;
  CapturedShowMessage: string;
  CapturedMessageDlgMsg: string;
  CapturedMessageDlgType: TMsgDlgType;
  CapturedMessageDlgButtons: TMsgDlgButtons;
  MockMessageDlgResult: TModalResult;

procedure ShowMessage(const Msg: string);
function MessageDlg(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint): Integer; overload;
function MessageDlg(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint; DefaultBtn: TMsgDlgBtn): Integer; overload;

implementation

procedure ShowMessage(const Msg: string);
begin
  CapturedShowMessage := Msg;
  if AutoProceed then
    PostMessage(Application.Handle, WM_CLOSE, 0, 0);
end;

function MessageDlg(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint): Integer;
begin
  CapturedMessageDlgMsg := Msg;
  CapturedMessageDlgType := DlgType;
  CapturedMessageDlgButtons := Buttons;
  Result := Integer(MockMessageDlgResult);
end;

function MessageDlg(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint; DefaultBtn: TMsgDlgBtn): Integer;
begin
  CapturedMessageDlgMsg := Msg;
  CapturedMessageDlgType := DlgType;
  CapturedMessageDlgButtons := Buttons;
  Result := Integer(MockMessageDlgResult);
end;

initialization
  AutoProceed := False;
  MockMessageDlgResult := mrOk;
end.

步骤2:配置测试项目

在测试项目的搜索路径中,将MockDialogs单元所在目录放在Delphi自带的Vcl目录之前,确保编译时优先引用Mock单元。

步骤3:修改测试单元

unit TestMain2;

interface

uses
  DUnitX.TestFramework, Main, MockDialogs, System.SysUtils, Windows, Vcl.Forms, Winapi.Messages;

type
  [TestFixture]
  TMyTestObject = class
  private
    procedure SimulateButtonClick;
  public
    [Setup]
    procedure Setup;
    [TearDown]
    procedure TearDown;
    [Test]
    procedure TestShowMessageTrigger;
    [Test]
    procedure TestMessageDlgInteraction;
  end;

implementation

var
  MainFormT: TMainForm;

procedure TMyTestObject.Setup;
begin
  // 重置Mock状态
  MockDialogs.AutoProceed := False;
  MockDialogs.CapturedShowMessage := '';
  MockDialogs.CapturedMessageDlgMsg := '';
  MockDialogs.MockMessageDlgResult := mrOk;

  MainFormT := TMainForm.Create(nil);
end;

procedure TMyTestObject.TearDown;
begin
  MainFormT.Free;
end;

procedure TMyTestObject.SimulateButtonClick;
begin
  MainFormT.Button1.Click;
end;

procedure TMyTestObject.TestShowMessageTrigger;
begin
  MockDialogs.AutoProceed := True;
  SimulateButtonClick;
  
  // 验证捕获的提示文本
  Assert.AreEqual('按钮点击后的预期提示', MockDialogs.CapturedShowMessage);
end;

procedure TMyTestObject.TestMessageDlgInteraction;
begin
  // 设置MessageDlg的Mock返回值
  MockDialogs.MockMessageDlgResult := mrCancel;
  // 假设Button2会触发MessageDlg
  MainFormT.Button2.Click;
  
  // 验证捕获的对话框参数
  Assert.AreEqual('确认执行此操作?', MockDialogs.CapturedMessageDlgMsg);
  Assert.AreEqual(mtConfirmation, MockDialogs.CapturedMessageDlgType);
  Assert.AreEqual([mbYes, mbNo], MockDialogs.CapturedMessageDlgButtons);
end;

initialization
  TDUnitX.RegisterTestFixture(TMyTestObject);

end.

方案2:运行时Hook(无需修改项目路径)

通过修改函数内存地址,动态替换官方ShowMessage和MessageDlg的实现,适合需要动态切换Mock的场景。

步骤1:添加Hook工具单元

unit HookUtils;

interface

uses
  Winapi.Windows, System.SysUtils;

type
  TShowMessageProc = procedure(const Msg: string);
  TMessageDlgProc = function(const Msg: string; DlgType: Integer; Buttons: Integer; HelpCtx: Longint): Integer;

procedure HookProcedure(var OriginalProc; NewProc: Pointer);
procedure UnhookProcedure(var OriginalProc; SavedProc: Pointer);

implementation

procedure HookProcedure(var OriginalProc; NewProc: Pointer);
var
  OldProtect: DWORD;
begin
  if VirtualProtect(@OriginalProc, SizeOf(Pointer), PAGE_EXECUTE_READWRITE, OldProtect) then
  begin
    PPointer(@OriginalProc)^ := NewProc;
    VirtualProtect(@OriginalProc, SizeOf(Pointer), OldProtect, OldProtect);
  end;
end;

procedure UnhookProcedure(var OriginalProc; SavedProc: Pointer);
var
  OldProtect: DWORD;
begin
  if VirtualProtect(@OriginalProc, SizeOf(Pointer), PAGE_EXECUTE_READWRITE, OldProtect) then
  begin
    PPointer(@OriginalProc)^ := SavedProc;
    VirtualProtect(@OriginalProc, SizeOf(Pointer), OldProtect, OldProtect);
  end;
end;

end.

步骤2:修改测试单元

unit TestMain2;

interface

uses
  DUnitX.TestFramework, Main, Vcl.Dialogs, System.SysUtils, Windows, Vcl.Forms, Winapi.Messages, HookUtils;

type
  TMockDialogs = class
  private
    class var FAutoProceed: Boolean;
    class var FCapturedShowMessage: string;
    class var FCapturedMessageDlgMsg: string;
    class var FCapturedMessageDlgType: TMsgDlgType;
    class var FCapturedMessageDlgButtons: TMsgDlgButtons;
    class var FMockMessageDlgResult: TModalResult;
    // 保存原始函数地址
    class var FOriginalShowMessage: TShowMessageProc;
    class var FOriginalMessageDlg: TMessageDlgProc;
    class procedure MockShowMessage(const Msg: string);
    class function MockMessageDlg(const Msg: string; DlgType: Integer; Buttons: Integer; HelpCtx: Longint): Integer;
  public
    class property AutoProceed: Boolean read FAutoProceed write FAutoProceed;
    class property CapturedShowMessage: string read FCapturedShowMessage;
    class property CapturedMessageDlgMsg: string read FCapturedMessageDlgMsg;
    class property CapturedMessageDlgType: TMsgDlgType read FCapturedMessageDlgType;
    class property CapturedMessageDlgButtons: TMsgDlgButtons read FCapturedMessageDlgButtons;
    class property MockMessageDlgResult: TModalResult read FMockMessageDlgResult write FMockMessageDlgResult;
    class procedure HookDialogs;
    class procedure UnhookDialogs;
  end;

  [TestFixture]
  TMyTestObject = class
  private
    procedure SimulateButtonClick;
  public
    [Setup]
    procedure Setup;
    [TearDown]
    procedure TearDown;
    [Test]
    procedure TestShowMessage;
    [Test]
    procedure TestMessageDlg;
  end;

implementation

class procedure TMockDialogs.MockShowMessage(const Msg: string);
begin
  FCapturedShowMessage := Msg;
  if FAutoProceed then
    PostMessage(Application.Handle, WM_CLOSE, 0, 0);
end;

class function TMockDialogs.MockMessageDlg(const Msg: string; DlgType: Integer; Buttons: Integer; HelpCtx: Longint): Integer;
begin
  FCapturedMessageDlgMsg := Msg;
  FCapturedMessageDlgType := TMsgDlgType(DlgType);
  FCapturedMessageDlgButtons := TMsgDlgButtons(Buttons);
  Result := Integer(FMockMessageDlgResult);
end;

class procedure TMockDialogs.HookDialogs;
begin
  // 保存原始函数地址
  FOriginalShowMessage := @Vcl.Dialogs.ShowMessage;
  FOriginalMessageDlg := @Vcl.Dialogs.MessageDlg;
  // 替换为Mock实现
  HookProcedure(FOriginalShowMessage, @MockShowMessage);
  HookProcedure(FOriginalMessageDlg, @MockMessageDlg);
end;

class procedure TMockDialogs.UnhookDialogs;
begin
  // 恢复原始函数
  UnhookProcedure(FOriginalShowMessage, @FOriginalShowMessage);
  UnhookProcedure(FOriginalMessageDlg, @FOriginalMessageDlg);
end;

var
  MainFormT: TMainForm;

procedure TMyTestObject.Setup;
begin
  // 重置Mock状态
  TMockDialogs.AutoProceed := False;
  TMockDialogs.CapturedShowMessage := '';
  TMockDialogs.CapturedMessageDlgMsg := '';
  TMockDialogs.MockMessageDlgResult := mrOk;

  MainFormT := TMainForm.Create(nil);
  // 启用Hook
  TMockDialogs.HookDialogs;
end;

procedure TMyTestObject.TearDown;
begin
  // 禁用Hook,避免影响其他测试
  TMockDialogs.UnhookDialogs;
  MainFormT.Free;
end;

procedure TMyTestObject.SimulateButtonClick;
begin
  MainFormT.Button1.Click;
end;

procedure TMyTestObject.TestShowMessage;
begin
  TMockDialogs.AutoProceed := True;
  SimulateButtonClick;
  
  Assert.AreEqual('预期提示文本', TMockDialogs.CapturedShowMessage);
end;

procedure TMyTestObject.TestMessageDlg;
begin
  TMockDialogs.MockMessageDlgResult := mrNo;
  MainFormT.Button2.Click;
  
  Assert.AreEqual('是否继续?', TMockDialogs.CapturedMessageDlgMsg);
  Assert.AreEqual(mtWarning, TMockDialogs.CapturedMessageDlgType);
  Assert.AreEqual([mbYes, mbNo], TMockDialogs.CapturedMessageDlgButtons);
end;

initialization
  TDUnitX.RegisterTestFixture(TMyTestObject);

end.

注意事项

  1. 单元重定向方案需确保搜索路径优先级正确,避免影响其他不需要Mock的单元。
  2. Hook方案必须在测试前后正确执行Hook和Unhook操作,防止函数地址被永久修改导致功能异常。
  3. 两种方案均无需修改被测试的原始单元,完全符合需求。

内容的提问来源于stack exchange,提问作者Ivan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 19:58:11