假设我有一个UserRepository结构,它包含了与数据库交互的逻辑。这个结构有一组方法,如:
还有另一个结构(让我们称之为UserService),它依赖于UserRepository结构。
要测试UserService,我需要模拟UserRepository的功能。我知道这样做的唯一方法是为UserRepository提供接口,并使UserService依赖于它而不是UserRepository结构。它将允许创建一个模拟的接口实现,并将其设置为测试中UserService的依赖项。
最常用的方法是什么?
1)如果UserService仅依赖于1个UserRepository的方法(让我们说findAll) - 我是否应该定义一个具有所有存储库方法的接口,或者更好地定义一个单独的仅用于此方法的接口并将其用作UserService的依赖项?如果是这样,它的最佳名称(界面)是什么?如果另一个struct依赖于findAll()和findById()方法,我应该再次创建另一个接口吗?
2)哪里有存储这些接口的模拟的最佳位置?是否可以重复使用它们?或者对于不同结构的测试,我需要重新定义模拟?
P.S。至于我,单元测试是项目中非常重要的一部分。我想让它们尽可能可读,避免使用样板代码并专注于它们的逻辑。因此,在不同的测试文件中为相同的接口创建几个模拟实现会给我一个糟糕的选择,因为它会使测试代码的可读性降低。
答案 0 :(得分:1)
1)我会使用Elevine所说的内容,即只需要该结构所需的方法。例如:您的UserService
需要FindByName
和FindAll
,而UserAdminService
需要FindById
,FindAll
和Save
。在这种情况下,您应该有两个接口:
UserProvider
与FindByName
和FindAll
UserAdminProvider
与FindById
,FindAll
和Save
。这也可以让您对UserProvider
进行检查,例如你知道它无法调用Save
,因此无法修改用户。
您可能只需要一个满足两个接口的实际实现。
2)查看testify/mock和mockery。 Mockery将在mocks
子包中为您的接口生成模拟,每个接口一个。
这意味着您不能对两个测试使用相同的模拟结构,但这并不重要,因为代码是生成的。你没有在mock结构中模拟接口的行为,你可以通过设置期望来测试它,例如:
func TestThatYouCantLoginWithNonexistentUser(t *testing.T) {
userRepository := new(mocks.UserRepository)
userService := user.NewService(userRepository)
// if the userService calls UserRepository.FindByName("joe"), it will return nil, since there's no such user.
userRepository.On("FindByName", "joe").Return(nil)
_, err := userService.Login("joe", "password")
// err should not be nil and the message should be "user does not exist"
assert.EqualError(t, err, "user does not exist")
// assert that the expectations were met, i.e. FindByName was called with "joe"
userRepository.AssertExpectations(t)
}
这实际上使测试易于理解,因为您不必在其他文件中检查模拟在调用FindByName("joe")
时所执行的操作。