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

在Haskell中Mock数据库连接 实现HUnit单元测试的方法

Haskell HUnit 单元测试Mock数据库操作实现方案

方案1:类型类抽象数据库操作(推荐中大型项目使用)

首先把数据库操作抽象为独立的类型类,业务代码只依赖抽象接口,不绑定具体的数据库驱动实现:

-- 定义数据库操作抽象接口
class Monad m => DBAccess m where
  queryUser :: UserId -> m (Maybe User)
  insertOrder :: Order -> m Int

业务函数改用类型类约束,移除对具体数据库连接的硬编码依赖:

-- 改造前:硬编码依赖数据库连接
-- getActiveUser :: Connection -> UserId -> IO (Maybe User)

-- 改造后:仅依赖抽象接口
getActiveUser :: DBAccess m => UserId -> m (Maybe User)
getActiveUser uid = do
  userMaybe <- queryUser uid
  pure $ userMaybe >>= \u -> if u.isActive then Just u else Nothing

实现测试专用的Mock实例,用状态存储预设的返回结果,不需要真实数据库连接:

import Control.Monad.State
import qualified Data.Map as Map

-- 自定义Mock Monad,用State存储预设返回值和调用记录
newtype MockDB a = MockDB { runMockDB :: State MockDBState a }
  deriving (Functor, Applicative, Monad, MonadState MockDBState)

-- Mock状态结构,可按需扩展调用记录、错误返回等配置
data MockDBState = MockDBState
  { mockUserResponses :: Map UserId (Maybe User)
  , mockInsertOrderReturns :: Map Order Int
  } deriving (Eq, Show)

-- 为Mock Monad实现数据库操作接口,直接读取预设值返回
instance DBAccess MockDB where
  queryUser uid = gets $ \s -> Map.findWithDefault Nothing uid s.mockUserResponses
  insertOrder order = gets $ \s -> Map.findWithDefault 0 order s.mockInsertOrderReturns

编写HUnit测试用例时,构造对应预设状态即可自定义返回结果:

import Test.HUnit

testGetActiveUser :: Test
testGetActiveUser = TestCase $ do
  let testUid = UserId 101
      activeUser = User { userId = testUid, isActive = True, userName = "test_user" }
      -- 预设查询testUid返回对应活跃用户
      mockState = MockDBState
        { mockUserResponses = Map.singleton testUid (Just activeUser)
        , mockInsertOrderReturns = Map.empty
        }
      -- 运行业务逻辑获取结果
      result = evalState (runMockDB $ getActiveUser testUid) mockState
  assertEqual "活跃用户查询结果匹配预期" (Just activeUser) result

方案2:函数参数传递(轻量场景使用)

如果业务逻辑简单,不想定义额外类型类,可直接把数据库操作作为参数传入业务函数,测试时传入自定义Mock函数即可:

-- 业务函数接收数据库查询操作作为参数
getActiveUser :: (UserId -> IO (Maybe User)) -> UserId -> IO (Maybe User)
getActiveUser queryFunc uid = do
  userMaybe <- queryFunc uid
  pure $ userMaybe >>= \u -> if u.isActive then Just u else Nothing

测试用例直接构造Mock函数,不需要真实数据库连接:

testGetInactiveUser :: Test
testGetInactiveUser = TestCase $ do
  let testUid = UserId 102
      inactiveUser = User { userId = testUid, isActive = False, userName = "inactive_user" }
      -- Mock查询函数,直接返回预设结果
      mockQuery = \uid -> if uid == testUid then pure (Just inactiveUser) else pure Nothing
  result <- getActiveUser mockQuery testUid
  assertEqual "非活跃用户应返回空" Nothing result

可选优化

如果需要验证数据库调用的参数、次数,可在MockDBState中新增queryCallLog :: [UserId]这类字段,每次调用queryUser时往日志里追加参数,测试结束后断言日志内容符合预期即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 05:06:01