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.
注意事项
- 单元重定向方案需确保搜索路径优先级正确,避免影响其他不需要Mock的单元。
- Hook方案必须在测试前后正确执行Hook和Unhook操作,防止函数地址被永久修改导致功能异常。
- 两种方案均无需修改被测试的原始单元,完全符合需求。
内容的提问来源于stack exchange,提问作者Ivan
相关产品推荐
相关产品推荐

