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

如何测试Fyne API ConfirmDialog内部的函数?

如何测试Fyne中ConfirmDialog的交互逻辑?

我用Fyne库开发时,通过ConfirmDialog确认用户是否需要覆盖存档文件,核心代码如下:

func (g *GunMenu) LoadButton(w *fyne.Window) (sb *widget.Button) {
    g.SaveButton = widget.NewButton("save", func() {
        fmt.Println("saving...")
        dialog.ShowConfirm("Saving...", "overwrite save?", func(b bool) {
            err := g.SaveData.SaveGame()
            if err != nil {
                dialog.NewError(err, *w)
            }
        }, *w)
    })

    return g.SaveButton
}

测试时只能触发按钮点击,无法模拟对话框的确认/取消操作,导致对话框内的代码无法被测试覆盖,测试代码如下:

func TestGetWidgets(t *testing.T) {
    tw := test.NewWindow(nil)
    gm := GunMenu{}
    gm.LoadButton(&tw)
    test.Tap(gm.SaveButton)
    test.Tap(gm.SaveButton)
}

请问有哪些测试该对话框的方法?是否需要将回调函数单独定义再做单元测试?


可行的测试方案

1. 抽离回调逻辑,单独做单元测试

把对话框内的业务逻辑从匿名回调中抽离成独立函数,直接针对业务逻辑做单元测试,无需依赖Fyne的UI组件。

修改后的业务代码:

func (g *GunMenu) handleSaveConfirm(confirm bool, w fyne.Window) {
    if !confirm {
        return // 取消操作直接返回,无需执行保存
    }
    err := g.SaveData.SaveGame()
    if err != nil {
        dialog.NewError(err, w)
    }
}

func (g *GunMenu) LoadButton(w *fyne.Window) (sb *widget.Button) {
    g.SaveButton = widget.NewButton("save", func() {
        fmt.Println("saving...")
        dialog.ShowConfirm("Saving...", "overwrite save?", func(b bool) {
            g.handleSaveConfirm(b, *w)
        }, *w)
    })
    return g.SaveButton
}

对应的单元测试:

func TestHandleSaveConfirm(t *testing.T) {
    // 模拟带Mock SaveData的GunMenu实例
    gm := &GunMenu{
        SaveData: &MockSaveData{ /* 用mock库模拟SaveGame的返回值 */ },
    }
    mockWindow := test.NewWindow(nil)

    // 测试确认保存的情况
    gm.handleSaveConfirm(true, mockWindow)
    // 断言SaveGame是否被调用、错误是否正确处理

    // 测试取消保存的情况
    gm.handleSaveConfirm(false, mockWindow)
    // 断言SaveGame未被调用
}

2. 用Fyne测试包模拟对话框交互

Fyne的test包提供了查找当前窗口对话框的方法,可直接获取ConfirmDialog实例,模拟点击确认或取消按钮。

修改后的测试代码:

func TestSaveButtonWithDialog(t *testing.T) {
    tw := test.NewWindow(nil)
    gm := GunMenu{
        SaveData: &MockSaveData{}, // 注入Mock的SaveData
    }
    gm.LoadButton(&tw)

    // 触发按钮点击,弹出对话框
    test.Tap(gm.SaveButton)

    // 查找当前窗口中的ConfirmDialog
    d := test.WinFindDialog(tw).(*dialog.ConfirmDialog)
    if d == nil {
        t.Fatal("未找到ConfirmDialog")
    }

    // 模拟点击确认按钮
    test.Tap(d.Affirm)
    // 断言SaveGame是否被调用

    // 再次触发按钮,测试取消逻辑
    test.Tap(gm.SaveButton)
    d = test.WinFindDialog(tw).(*dialog.ConfirmDialog)
    test.Tap(d.Negative)
    // 断言SaveGame未被调用
}

3. 依赖注入对话框服务,解耦UI与业务

定义对话框服务接口,生产环境用Fyne实现,测试时用Mock实现,完全控制对话框的回调触发,无需实际渲染UI。

定义接口与实现:

type DialogService interface {
    ShowConfirm(title, message string, callback func(bool), parent fyne.Window)
}

// 生产环境实现
type FyneDialogService struct{}

func (f *FyneDialogService) ShowConfirm(title, message string, callback func(bool), parent fyne.Window) {
    dialog.ShowConfirm(title, message, callback, parent)
}

// 测试用Mock实现
type MockDialogService struct {
    ShowConfirmCalled bool
    LastCallback      func(bool)
}

func (m *MockDialogService) ShowConfirm(title, message string, callback func(bool), parent fyne.Window) {
    m.ShowConfirmCalled = true
    m.LastCallback = callback
}

修改GunMenu代码,注入DialogService:

type GunMenu struct {
    SaveButton      *widget.Button
    SaveData        SaveDataInterface
    DialogService   DialogService // 注入对话框服务
}

func (g *GunMenu) LoadButton(w *fyne.Window) (sb *widget.Button) {
    g.SaveButton = widget.NewButton("save", func() {
        fmt.Println("saving...")
        g.DialogService.ShowConfirm("Saving...", "overwrite save?", func(b bool) {
            err := g.SaveData.SaveGame()
            if err != nil {
                dialog.NewError(err, *w)
            }
        }, *w)
    })
    return g.SaveButton
}

测试代码:

func TestSaveButtonWithMockDialog(t *testing.T) {
    tw := test.NewWindow(nil)
    mockDialog := &MockDialogService{}
    mockSaveData := &MockSaveData{}

    gm := &GunMenu{
        SaveData:       mockSaveData,
        DialogService:  mockDialog,
    }
    gm.LoadButton(&tw)

    // 触发按钮点击
    test.Tap(gm.SaveButton)

    // 断言对话框服务被调用
    if !mockDialog.ShowConfirmCalled {
        t.Fatal("对话框服务未被调用")
    }

    // 模拟用户点击确认
    mockDialog.LastCallback(true)
    // 断言SaveGame被调用

    // 模拟用户点击取消
    mockDialog.LastCallback(false)
    // 断言SaveGame未被调用
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 11:07:10