如果不在测试中调用接口的行为,模拟接口的行为是没有用的

问题描述 投票:1回答:1

我是否需要模拟不调用的接口,例如用户名和密码字段为空?我正在尝试先编写测试,但对于是否应使用模拟方法感到困惑。

我的登录测试

   private val authRepository: AuthRepository = mockk()

    private val userManager: AccountManager = mockk()

    private lateinit var authUseCase: AuthUseCase

    @BeforeEach
    fun setUp() {
        clearMocks(authRepository)
        clearMocks(userManager)
        authUseCase = AuthUseCase(authRepository, userManager)
    }




   /**
     *  Scenario: Login check with empty fields:
     * * Given I am on the login page
     * * When I enter empty username
     *   And I enter empty password
     *   And I click on the "Login" button
     * * Then I get empty fields error.
     */


   @Test
    fun `Empty fields result empty fields error`() {

        // Given

        // When
        val expected = authUseCase.login("", "", false)

        // Then
        verify(exactly = 0) { authRepository.login(or(any(), ""), or(any(), ""), any()) }
        expected assertEquals EMPTY_FIELD_ERROR

    }

即使由于用户名和/或字段为空而未调用它们,我是否也必须为测试或accountManager的给定部分模拟接口?

这是我打算在测试后编写的登录方法的最终版本

  class AuthUseCase(
    private val authRepository: AuthRepository,
    private val accountManager: AccountManager
) {

    private var loginAttempt = 1
    /*
        STEP 1: Throw exception for test to compile and fail
     */
//    fun login(
//        userName: String,
//        password: String,
//        rememberMe: Boolean = false
//    ): AuthenticationState {
//        throw NullPointerException()
//    }


    /*
        STEP3: Check if username or password is empty
     */
//        fun login(
//        userName: String,
//        password: String,
//        rememberMe: Boolean = false
//    ): AuthenticationState {
//
//
//       if (userName.isNullOrBlank() || password.isNullOrBlank()) {
//           return EMPTY_FIELD_ERROR
//       }else {
//           throw NullPointerException()
//       }
//
//    }


    /**
     * This is the final and complete version of the method.
     */
    fun login(
        userName: String,
        password: String,
        rememberMe: Boolean
    ): AuthenticationState {


        return if (loginAttempt >= MAX_LOGIN_ATTEMPT) {
            MAX_NUMBER_OF_ATTEMPTS_ERROR
        } else if (userName.isNullOrBlank() || password.isNullOrBlank()) {
            EMPTY_FIELD_ERROR
        } else if (!checkUserNameIsValid(userName) || !checkIfPasswordIsValid(password)) {
            INVALID_FIELD_ERROR
        } else {

            // Concurrent Authentication via mock that returns AUTHENTICATED, or FAILED_AUTHENTICATION
            val authenticationPass =
                getAccountResponse(userName, password, rememberMe)


            return if (authenticationPass) {

                loginAttempt = 0
                AUTHENTICATED

            } else {

                loginAttempt++
                FAILED_AUTHENTICATION

            }
        }


    }

    private fun getAccountResponse(
        userName: String,
        password: String,
        rememberMe: Boolean
    ): Boolean {

        val authResponse =
            authRepository.login(userName, password, rememberMe)

        val authenticationPass = authResponse?.authenticated ?: false

        authResponse?.token?.let {
            accountManager.saveToken(it)
        }

        return authenticationPass
    }


    private fun checkUserNameIsValid(field: String): Boolean {
        return field.length >15 && field.endsWith("@example.com")

    }

    private fun checkIfPasswordIsValid(field: String): Boolean {
        return field.length in 6..10
    }

}

我应该仅在所有其他状态都通过时才进行模拟,并且从存储库中获得模拟响应并与客户经​​理进行交互吗?

测试部分应给予什么?

unit-testing tdd mockk
1个回答
0
投票

我建议您不需要在“空字段结果为空字段错误”测试中进行验证。我还建议您为每个空白字段编写单独的测试。如果您执行严格的TDD,则在编写代码时将测试每个条件。即“空用户名应该出错”将是第一个测试,并且要测试第一个条件,然后是“空用户名应该出错”,然后进行下一步(在您完成两次单独的编写第二次测试后,您的代码可能看起来像]

if (userName.isNullOrBlank()) {
  return EMPTY_FIELD_ERROR
}
if (password.isNullOrBlank() {
  return EMPTY_FIELD_ERROR
}

一旦以上两个测试都通过,您可以重构为

if (userName.isNullOrBlank() || password.isNullOrBlank()) {
            EMPTY_FIELD_ERROR
}

一旦开始测试checkUserNameIsValid和checkIfPasswordIsValid的条件语句,您将需要在类中引入authRepository和accountManager(构造函数注入),然后您将在使用它们时开始模拟调用。通常,模拟框架会伪造一个对象(即代码将运行,但不会返回任何有意义的结果)。当您要测试特定行为时,应以返回实际的模拟数据为目标,即在测试成功登录时,应从authRepository.login返回有效对象。通常,我不使用@BeforeEach中的设置方法,而使用工厂方法或构建器来创建我的受测类。我不熟悉kotlin语法,因此充其量只能做一些sudo代码来演示您的生成器或工厂函数的外观。

// overloaded factory function
fun create() {
  val authRepository: AuthRepository = mockk()
  val userManager: AccountManager = mockk()
  return AuthUseCase(authRepository, userManager);
}


fun create(authRepository: AuthRepository) {
  val userManager: AccountManager = mockk()
  return AuthUseCase(authRepository, userManager);
}


fun create(authRepository: AuthRepository, userManager: AccountManager) {
  return AuthUseCase(authRepository, userManager);
}

[您将需要了解如何在kotlin中创建构建器,但最终结果是,构建器总是开始将被测类的依赖项设置为模拟,它什么也不做,但允许您进行更改那些模拟。

例如

AuthUseCase authUseCase = AuthUseCaseBuilder.Create().WithAuthRepository(myMockAuthRepository).Build();

最后一件事。我故意在上面讨论了loginAttempt检查,因为AuthUseCase是一个服务类,将由多个用户使用,并且在请求的生命周期内都有效,在这种情况下,您不想维护该类内的状态(即loginAttempt变量与该类具有相同的生存期)。最好在数据库表中记录每个用户名的尝试次数,并且每次成功登录后都需要重置尝试次数。

希望这会有所帮助。

© www.soinside.com 2019 - 2024. All rights reserved.