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

使用ScalaMock测试ScyllaDB操作代码时遇NullPointerException

解决ScalaMock测试StudentRepository时的NullPointerException问题

错误原因

直接mock StudentDB 会触发其构造函数执行,而StudentDB继承的Database父类在初始化时需要依赖CassandraConnection的有效实例。ScalaMock创建mock对象时未传入合法的CassandraConnection,导致父类构造过程中访问null对象抛出NullPointerException。

另外,StudentRepository实际依赖的是StudentDB内部Student schema的操作(select.all().fetch、store(student).future()),而非StudentDB本身,直接mock整个StudentDB不仅容易触发构造错误,也不符合测试的最小依赖原则。

解决方案

推荐采用抽象依赖接口的方式,将数据访问逻辑封装到独立接口中,让StudentRepository依赖接口而非具体的StudentDB类,这样测试时可以轻松mock接口实现。

步骤1:抽象数据访问接口

定义一个包含所需操作的接口,让StudentDB实现该接口:

import scala.concurrent.Future
import com.datastax.driver.core.ResultSet

trait StudentDao {
  def getAllStudents: Future[List[StudentData]]
  def insertStudent(student: StudentData): Future[ResultSet]
}

class StudentDB(override val connector: CassandraConnection)
  extends Database[StudentDB](connector) with StudentDao {
    object Student extends StudenSchema with Connector

    // 实现接口方法
    override def getAllStudents: Future[List[StudentData]] = {
      Student.select.all().fetch
    }

    override def insertStudent(student: StudentData): Future[ResultSet] = {
      Student.store(student).future()
    }
}

步骤2:修改StudentRepository依赖接口

调整StudentRepository,使其依赖抽象的StudentDao而非具体的StudentDB:

class StudentRepository @Inject()(studentDao: StudentDao) extends StudentRepositoryTrait {

  override def getStudents: Future[List[StudentData]] = {
    studentDao.getAllStudents
  }

  override def insertStudentData(student: StudentData): Future[ResultSet] = {
    studentDao.insertStudent(student)
  }
}

步骤3:编写Mock测试用例

现在可以直接mockStudentDao接口,指定方法的返回结果并验证调用:

import scala.concurrent.Future
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalamock.scalatest.MockFactory
import com.datastax.driver.core.ResultSet

class RepositoryMockTesting extends AnyFlatSpec with Matchers with MockFactory {

  val mockStudentDao: StudentDao = mock[StudentDao]
  val studentRepository = new StudentRepository(mockStudentDao)

  val testStudent: StudentData = StudentData(Some("Zaman Khan"), Some("1"), Some(""), Some(""), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None)
  val mockStudentList = List(testStudent)
  val mockResultSet = mock[ResultSet]

  "StudentRepository" should "fetch non-empty student list" in {
    // 预期getAllStudents方法被调用,返回模拟的学生列表
    (mockStudentDao.getAllStudents _).expects().returning(Future.successful(mockStudentList))

    val result = studentRepository.getStudents
    result.map { list =>
      list should not be empty
      list shouldEqual mockStudentList
    }
  }

  it should "successfully insert student data" in {
    // 预期insertStudent方法被传入testStudent,返回模拟的ResultSet
    (mockStudentDao.insertStudent _).expects(testStudent).returning(Future.successful(mockResultSet))
    // 预期ResultSet的wasApplied方法返回true
    (mockResultSet.wasApplied _).expects().returning(true)

    val result = studentRepository.insertStudentData(testStudent)
    result.map { rs =>
      rs.wasApplied() shouldBe true
    }
  }
}

补充说明

如果不想大幅修改现有代码,也可以尝试创建一个带有mockCassandraConnection的StudentDB实例,再mock其内部Student对象的方法,但这种方式复杂度较高,不如抽象接口的方式简洁易维护。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 22:30:21