我是从事Kotlin单元测试用例的android开发人员,并且具有功能
class ExpiryDateValidator @Inject constructor() {
var isValid = false
fun isValidExpiryDate(expiryDate: String): Boolean {
if (expiryDate.isNotEmpty() && expiryDate.length == EXPIRY_DATE_LENGTH) {
val dateFormat = DateTimeFormat.forPattern(EXPIRY_DATE_FORMAT)
val dateFormatted = dateFormat.parseDateTime(expiryDate)
val firstOfNextMonth = dateFormatted.plusMonths(1)
val lastOfSelectedMonth = firstOfNextMonth.minusDays(1)
isValid = lastOfSelectedMonth.withTimeAtStartOfDay().isAfterNow
}
return isValid
}
companion object {
const val EXPIRY_DATE_FORMAT = "MM/YY"
const val EXPIRY_DATE_LENGTH = 5
}}
///////////////////////////////////////////////// ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// ///
interface EnterCardDetailsViewModelInputs : PaymentsViewModelInputs {
fun onTextFieldChanged(name: String, cardNumber: String, expiryDate: String, cvv: String, issueNumber: String)
fun onExpiryDateChanged(expiryDate: String)
fun onButtonClicked(card: NewCardUIModel, cvv: String, saveCard: Boolean)
}
interface EnterCardDetailsViewModelOutputs : PaymentsViewModelOutputs {
fun payButtonEnabled(): Observable<Boolean>
fun expiryDateFormatted(): Observable<String>
fun paymentSuccessful(): Observable<String>
fun addCardSuccessful(): Observable<Unit>
fun getViewsText(): Observable<Pair<Int, Int>>
fun getExtraFieldsVisibility(): Observable<Int>
}
class EnterCardDetailsViewModel @Inject constructor(application: Application) : PaymentsViewModel(application),
EnterCardDetailsViewModelInputs,
EnterCardDetailsViewModelOutputs {
override val inputs: EnterCardDetailsViewModelInputs
get() = this
override val outputs: EnterCardDetailsViewModelOutputs
get() = this
private val payButtonEnabled = PublishSubject.create<Boolean>()
private val expiryDateFormatted = PublishSubject.create<String>()
private val paymentSuccessful = PublishSubject.create<String>()
private val getViewsText = PublishSubject.create<Pair<Int, Int>>()
private val getSwitchVisibility = PublishSubject.create<Int>()
private val addCardSuccessful = PublishSubject.create<Unit>()
@Inject
lateinit var premiseUseCase: PremiseUseCase
@Inject
lateinit var userActionUseCase: UserDataActionUseCase
@Inject
lateinit var expiryDateValidator: ExpiryDateValidator
override fun setAmount(paymentAmount: String?) {
super.setAmount(paymentAmount)
if (paymentAmount == null) {
getViewsText.onNext(Pair(R.string.enter_card_details_add_title, R.string.enter_card_details_save_button))
getSwitchVisibility.onNext(View.GONE)
return
}
getSwitchVisibility.onNext(View.VISIBLE)
getViewsText.onNext(Pair(R.string.enter_card_details_pay_title, R.string.enter_card_details_pay_button))
}
override fun onTextFieldChanged(
name: String,
cardNumber: String,
expiryDate: String,
cvv: String,
issueNumber: String
) {
val nameTrimmed = name.trim()
val validName = isValidName(nameTrimmed)
val validCardNumber = isValidCardNumber(cardNumber)
val validExpiryDate = expiryDateValidator.isValidExpiryDate(expiryDate)
val validCvv = isValidCVV(cvv)
val validIssueNumber = isValidIssueNumber(issueNumber)
payButtonEnabled.onNext(validName && validCardNumber && validExpiryDate && validCvv && validIssueNumber)
}
override fun onExpiryDateChanged(expiryDate: String) {
if (expiryDate.matches(EXPIRY_DATE_REGEX_ONE) && expiryDate.length == 2) {
// it adds symbol / when there are 2 digits inputted in order to get MM/YY
expiryDateFormatted.onNext("$expiryDate/")
}
}
override fun expiryDateFormatted(): Observable<String> {
return expiryDateFormatted.observeOn(schedulerProvider.ui()).hide()
}
override fun onButtonClicked(card: NewCardUIModel, cvv: String, saveCard: Boolean) {
val cardUI = CardUIModel(
cardDigits = card.cardNumber,
cardType = "",
cardExpiry = card.expiryDate,
validFrom = "",
cardToken = "",
isAutoTopUp = "",
cvv = cvv,
amount = paymentAmount
)
if (paymentAmount == null) {
paymentsUseCase.saveCard(cardUI)
.doOnSubscribe { refreshing.onNext(true) }
.doFinally { refreshing.onNext(false) }
.compose(schedulerProvider.doOnIoObserveOnMainSingle())
.subscribe({
addCardSuccessful.onNext(Unit)
}, {
jarvis.trackEvent(
ErrorAnalyticsEvent(
ErrorAnalyticsEvent.ERROR_ENTER_CARD_SAVE,
ErrorAnalyticsEvent.IS_MSG
)
)
jarvis.logError("Failed saving card", it)
if (it is HttpException && it.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
error.onNext(R.string.enter_card_details_save_card_error)
} else {
error.onNext(R.string.enter_card_details_save_card_error_generic)
}
})
.also { subscriptions.add(it) }
return
}
val expiryDateFormatted = formatCardExpiryDate(card.expiryDate.replace("/", ""))
paymentsUseCase.registerAndPay(cardUI, saveCard, expiryDateFormatted)
.doOnSubscribe { refreshing.onNext(true) }
.doFinally { refreshing.onNext(false) }
.flatMap { paymentSuccessful ->
if (paymentSuccessful) {
userActionUseCase.payment()
return@flatMap premiseUseCase.getContractStatus()
} else {
throw PaymentFlowImpl.PaymentFlowException(
PaymentFlowImpl.PaymentFlowError.PAYMENT_ERROR, Exception("Failed making a Payment")
)
}
}
.compose(schedulerProvider.doOnIoObserveOnMainSingle())
.subscribe(
{
paymentSuccessful.onNext(it.accountBalance)
}, {
if (it is PaymentFlowImpl.PaymentFlowException) {
if (it.paymentFlowError == PaymentFlowImpl.PaymentFlowError.PAYMENT_ERROR) {
//error paying
jarvis.trackEvent(
ErrorAnalyticsEvent(
ErrorAnalyticsEvent.ERROR_ENTER_CARD_PAYMENT,
ErrorAnalyticsEvent.IS_MSG
)
)
jarvis.logError("Failed making a Payment", it)
jarvis.trackEvent(
ErrorDisplayedAnalyticsEvent(
errorMsg = ErrorDisplayedAnalyticsEvent.PAYMENT_NOT_SUCCESSFUL,
errorProcess = ErrorDisplayedAnalyticsEvent.PAYMENT
)
)
if (it.exception is HttpException && it.exception.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
error.onNext(R.string.enter_card_details_save_card_error)
} else {
error.onNext(R.string.enter_card_details_save_card_error_generic)
}
} else {
//error saving card
userActionUseCase.payment()
paymentSuccessful.onNext("")
}
} else {
jarvis.logError("Failed making a Payment", it)
jarvis.trackEvent(
ErrorDisplayedAnalyticsEvent(
errorMsg = ErrorDisplayedAnalyticsEvent.PAYMENT_NOT_SUCCESSFUL,
errorProcess = ErrorDisplayedAnalyticsEvent.PAYMENT
)
)
error.onNext(R.string.enter_card_details_save_card_error_generic)
}
})
.also { subscriptions.add(it) }
}
override fun paymentSuccessful(): Observable<String> {
return paymentSuccessful.observeOn(schedulerProvider.ui()).hide()
}
override fun payButtonEnabled(): Observable<Boolean> {
return payButtonEnabled.observeOn(schedulerProvider.ui()).hide()
}
override fun getViewsText(): Observable<Pair<Int, Int>> {
return getViewsText.observeOn(schedulerProvider.ui()).hide()
}
override fun getExtraFieldsVisibility(): Observable<Int> {
return getSwitchVisibility.observeOn(schedulerProvider.ui()).hide()
}
override fun addCardSuccessful(): Observable<Unit> {
return addCardSuccessful.observeOn(schedulerProvider.ui()).hide()
}
fun isValidName(nameOnCard: String): Boolean {
return nameOnCard.isNotEmpty().and(nameOnCard.matches(NAME_REGEX))
}
fun isValidCardNumber(cardNumber: String): Boolean {
return cardNumber.length in 11..19
}
fun isValidIssueNumber(issueNumber: String): Boolean {
return issueNumber.isEmpty().or(issueNumber.matches(NUMBER_REGEX))
}
companion object {
val NUMBER_REGEX = Regex("[0-9]+")
val NAME_REGEX = Regex("[a-zA-Z ]+")
val EXPIRY_DATE_REGEX_ONE = Regex("[0-9]")
}
}
///////////////////////////////////////////////// ///////////////////////////////////////////////////// //////////////////////////////////// < / p>
class EnterCardDetailsViewModelSpec : Spek({
val viewModel by memoized {
EnterCardDetailsViewModel(mockApplication()).apply {
mock(this)
this.expiryDateValidator = Mockito.mock(ExpiryDateValidator::class.java)
}
}
val viewModelSpy = Mockito.spy(viewModel)
lateinit var boolTestSub: TestObserver<Boolean>
val subscriberError by memoized { TestObserver.create<Int>() }
beforeEachTest {
Mockito.clearInvocations(viewModelSpy)
}
describe("isValidPaymentAmount") {
mapOf(
"a" to true,
"" to false,
"a2" to false,
"a 2" to false,
"name surname" to true,
"name surname." to false,
"1name surname" to false,
",: ][" to false,
"9999" to false
).forEach { map ->
on("nameOnCard: ${map.key}, isvalid: ${map.value}") {
it("should validate name on card") {
Assert.assertEquals(map.value, viewModelSpy.isValidName(map.key))
}
it("should not emit errors") {
subscriberError.assertNoValues()
}
}
}
}
describe("isValidCardNumber") {
mapOf(
"1" to false,
"" to false,
"a2" to false,
"a 2" to false,
"1111222233334444" to true,
"11112222333344442222" to false,
" 22" to false,
",: ][" to false,
"9999" to false
).forEach { map ->
on("cardNumber: ${map.key}, isvalid: ${map.value}") {
it("should validate card number") {
Assert.assertEquals(map.value, viewModelSpy.isValidCardNumber(map.key))
}
it("should not emit errors") {
subscriberError.assertNoValues()
}
}
}
}
describe("isValidCVV") {
viewModelSpy.setAmount("1.00")
mapOf(
"1" to false,
"" to false,
"a2" to false,
"11" to false,
"111" to true,
" 22" to false,
",: ][" to false,
"9999" to true
).forEach { map ->
on("cvv: ${map.key}, isvalid: ${map.value}") {
it("should validate cvv") {
Assert.assertEquals(map.value, viewModelSpy.isValidCVV(map.key))
}
it("should not emit errors") {
subscriberError.assertNoValues()
}
}
}
}
describe("isValidIssueNumber") {
mapOf(
"1" to true,
"" to true,
"a2" to false,
"11" to true,
"111" to true,
" 22" to false,
",: ][" to false,
"9999" to true
).forEach { map ->
on("IssueNumber: ${map.key}, isvalid: ${map.value}") {
it("should validate IssueNumber") {
Assert.assertEquals(map.value, viewModelSpy.isValidIssueNumber(map.key))
}
it("should not emit errors") {
subscriberError.assertNoValues()
}
}
}
}
describe("isValidExpiryDate") {
mapOf(
"12/20" to true,
"12/201" to false,
"" to false,
"19/20" to false,
"/" to false,
"/22" to false
).forEach { map ->
on("Date: ${map.key}, isvalid: ${map.value}") {
it("should validate ExpiryDate") {
Assert.assertEquals(map.value, viewModelSpy.expiryDateValidator.isValidExpiryDate(map.key))
}
it("should not emit errors") {
subscriberError.assertNoValues()
}
}
}
}
describe("onTextFieldChanged") {
beforeEachTest {
Mockito.clearInvocations(viewModelSpy)
boolTestSub = viewModelSpy.outputs.payButtonEnabled().test()
}
mapOf(
listOf("name", "1111222244445555", "12/20", "111", "asd") to false,
listOf("name", "1111222244445555", "12/20", "111", "") to true,
listOf("name1", "1111222244445555", "12/20", "111", "") to false,
listOf("name", "11112222444455551111", "12/20", "111", "") to false,
listOf("name", "1111222244445555", "12/201", "111", "") to false,
listOf("name", "1111222244445555", "12/20", "11", "") to false,
listOf("", "1111222244445555", "12/20", "111", "") to false,
listOf("name", "", "12/20", "111", "asd") to false,
listOf("name", "1111222244445555", "", "111", "asd") to false,
listOf("name", "1111222244445555", "12/20", "", "asd") to false,
listOf("", "", "", "", "") to false
).forEach { params, isValid ->
on(
"name: ${params[0]}, cardNumber: ${params[1]}, expiryDate: ${params[2]}," +
" validCvv: ${params[3]}, validIssueNumber: ${params[4]},isValid: $isValid"
) {
viewModelSpy.inputs.onTextFieldChanged(params[0], params[1], params[2], params[3], params[4])
it("should emit continue button state") {
boolTestSub.assertValue(isValid)
}
}
}
}
})
基本上,当我添加测试用例isValidExpiryDate时,我的测试在位上升失败,并且我无法成功构建android构建。 请指导我,因为我是android的新手,无法弄清为什么它在比特币上失败了。