如何正确编写Plotly Dash回调函数的测试用例?
报错根因
你直接调用被@app.callback装饰后的函数时触发的报错。@app.callback会给原函数包一层上下文处理逻辑,运行时需要框架自动注入outputs_list等内部参数,你手动调用时没有传入这些参数,自然就抛出KeyError: 'outputs_list'。
解决方案
下面是两种最常用的测试方案,按推荐优先级排序:
方案1:拆分业务逻辑与回调装饰(最推荐)
把回调里的业务逻辑抽成无依赖的纯函数,回调本身只做上下文读取和纯函数调用,测试时直接测纯函数即可,完全不需要适配Dash的规则:
- 调整
index.py代码:
# 抽离的纯业务逻辑,无任何Dash依赖 def process_row_selection(row1: list, row2: list, is_triggered: bool) -> dict: if not is_triggered: return {'row-index-v1': [0], 'row-index-v2': [0]} return {'row-index-v1': row1, 'row-index-v2': row2} @app.callback( Output('rows-store', 'data'), Input('datatable1', 'selected_rows'), Input('datatable2', 'selected_rows'), ) def store_row_selection(row1: list, row2: list) -> dict: ctx = dash.callback_context return process_row_selection(row1, row2, bool(ctx.triggered))
- 编写测试用例:
# 直接测试纯函数,不需要任何Dash相关的fixture def test_store_row_selection(): # 测试回调被触发的场景 r1 = [0] r2 = [1] result = process_row_selection(r1, r2, is_triggered=True) assert isinstance(result, dict) assert result['row-index-v1'] == [0] assert result['row-index-v2'] == [1] # 测试初始未触发的场景 init_result = process_row_selection(r1, r2, is_triggered=False) assert init_result == {'row-index-v1': [0], 'row-index-v2': [0]}
方案2:直接调用原始函数+模拟上下文
如果不想改原有代码结构,可以通过__wrapped__属性拿到被装饰前的原始函数,同时模拟dash.callback_context的值来测试:
测试用例示例:
def test_store_row_selection(monkeypatch): r1 = [0] r2 = [1] # 模拟回调被触发的场景 mock_ctx = type('MockCtx', (), {'triggered': [{'prop_id': 'datatable1.selected_rows'}]})() monkeypatch.setattr('dash.callback_context', mock_ctx) # 调用被装饰前的原始函数 result = store_row_selection.__wrapped__(r1, r2) assert isinstance(result, dict) assert result['row-index-v1'] == [0] # 模拟初始未触发的场景 mock_ctx_no_trigger = type('MockCtx', (), {'triggered': []})() monkeypatch.setattr('dash.callback_context', mock_ctx_no_trigger) init_result = store_row_selection.__wrapped__(r1, r2) assert init_result == {'row-index-v1': [0], 'row-index-v2': [0]}
如果需要测试完整的交互流程,可以用Dash官方的dash.testing工具启动服务模拟用户操作,不过单元测试场景不需要用到这种重方案。
内容的提问来源于stack exchange,提问作者Kit Law
相关产品推荐
相关产品推荐

