0

I was developing some business app, which made as backend services of other Android app, which mange the franted content showed to the user.

My app work manage some intent recive fro an API call, doing some action based on this intent, and returning the raw data to the fronted app.

So, in oder to test it, I think in the implemention of a test function, but I get in to the trobble of how invoke the onActivityResult() from the testing context:

I have something like that:

MainActivity.kt


@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    private lateinit var viewModel: MainViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            var message = getString(R.string.processing)
            val request = HioPosRequest.forIntent(intent = intent)
            //Put here because we need access to Compose context to manage SharePreferences.
            viewModel = hiltViewModel()
            viewModel.launchProcessing(request).also {
                lifecycleScope.launch(Dispatchers.IO) {
                    viewModel.hioPosIntentResult.collect() {
                        if (it != null) {
                            sendResult(it)
                        }
                    }
                }
                lifecycleScope.launch(Dispatchers.IO) {
                    viewModel.uiMessage.collect() { state ->
                        message = state
                    }
                }
                lifecycleScope.launch(Dispatchers.IO){
                    viewModel.finnishMainIntent.collect(){
                        //if(it) finish()
                        Log.e("trace", "Invoca el Main Intent")
                    }
                }
            }
            HioPosConnectorTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Message(message)
                }
            }
        }
    }
    private fun sendResult(hioPosResponseIntent: HioPosResponseIntent) {
        if(hioPosResponseIntent.isError){
            this.setResult(Activity.RESULT_CANCELED, hioPosResponseIntent.resultIntent)
            this.finish()
        }else{
            this.setResult(Activity.RESULT_OK, hioPosResponseIntent.resultIntent)
            this.finish()
        }
    }
}
@Composable
fun Message(name: String, modifier: Modifier = Modifier) {
    Text(
        text = name,
        modifier = modifier
            .padding(16.dp)
            .absoluteOffset(x = 0.dp, y = ((LocalConfiguration.current.screenHeightDp) / 2.5).dp),
        textAlign = TextAlign.Center,
        style = (MaterialTheme.typography).bodyMedium
    )
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    HioPosConnectorTheme {
        Message("Processing")
    }
}

MainViewModel.kt


@HiltViewModel
class MainViewModel @Inject constructor(@ApplicationContext context: Context,private val sharedPreferences: SharedPreferences) : ViewModel() {

    private var _UiMessage: MutableStateFlow<String> = MutableStateFlow(context.getString(R.string.processing))

    val uiMessage: StateFlow<String> = _UiMessage.asStateFlow()

    private var _hioPosIntentResult: MutableStateFlow<HioPosResponseIntent?> = MutableStateFlow(null)
    val hioPosIntentResult: StateFlow<HioPosResponseIntent?> = _hioPosIntentResult.asStateFlow()

    private var _finishMainIntent: MutableStateFlow<Boolean> = MutableStateFlow(false)
    val finnishMainIntent : StateFlow<Boolean> = _finishMainIntent.asStateFlow()

    val contextPromp: Context = context  // This warning is a bug of Hilt, it's doesn't leak: https://stackoverflow.com/questions/66216839/inject-context-with-hilt-this-field-leaks-a-context-object
    fun launchProcessing(request: HioPosRequest) {


        viewModelScope.launch(Dispatchers.IO) {
            try {
                val shouldBeFinished: Boolean = when (request.action) {
                    ActionName.Main -> {
                        _finishMainIntent.value = true
                        false
                    }
                    ActionName.Initialize -> {
                        val useCase = InitializeUseCase(
                            request = request,
                            sharedPreferences = sharedPreferences
                        )
                        useCase.getInitializeTransation().also {
                            _hioPosIntentResult.value = it
                        }
                        true
                    }

                    ActionName.ShowSetup -> {
                        val useCase = ShowSetupUseCase(
                            request = request
                        )
                        useCase.getshowSetup().also {
                            _hioPosIntentResult.value = it
                        }
                        true
                    }

                    ActionName.Finalize -> {
                        val useCase = FinalizeUseCase(
                            request = request,
                            pinpadApi = AppModule().providePINPADApi()
                        )
                        useCase.getFinalizeProgress().also {
                            _hioPosIntentResult.value = it
                        }
                        true
                    }

                    ActionName.GetBehavior -> {
                        val useCase = GetBehaviorUseCase(
                            request = request
                        )
                        useCase.getBehaviour().also {
                            _hioPosIntentResult.value = it
                        }
                        true
                    }

                    ActionName.GetVersion -> {
                        val useCase = GetVersionUseCase(
                            request = request
                        )
                        useCase.getVersion().also {
                            _hioPosIntentResult.value = it
                        }
                        true
                    }

                    ActionName.CustomParams -> {
                        val useCase = CustomParamsUseCase(
                            request = request
                        )

                        useCase.loadParams(context = contextPromp).also {
                            _hioPosIntentResult.value = it
                        }
                        true
                    }

                    ActionName.Transaction -> {
                        val transactionUseCase = TransactionUseCase(
                            request = request,
                            transaccinApi = AppModule().provideTransaccinApi(),
                            uiMessage = _UiMessage,
                            context = contextPromp
                        )
                        val batchCloseUseCase = BatchCloseUseCase(
                            request = request,
                            pinpadApi = AppModule().providePINPADApi()
                        )
                        val transaction = request.transactionRequest()
                        if (transaction.transactionType == TransactionType.BatchClose) {
                            startShiftClose(batchCloseUseCase)
                        } else {
                            startTransaction(transaction, transactionUseCase)
                        }
                        true
                    }

                    else -> {false}
                }
                if (shouldBeFinished) {
                    // Todos los requests deberían finalizar la activity salvo que sea una transacción
                    // que abre otras activities. Si entra en esta condición es porque no se llamó
                    // correctamente a sendOkResult o sendErrorResult, revisar que ninguna condición haga
                    // return antes de setear el resultado.
                    throw Exception("Error finalizing request, undetermined state")
                }
            } catch (e: Exception) {
                Log.e("error", "error: ${e.message}", e)

            }
        }
    }
    /*fun finalizeShiftClose(apiResponse: ApiWorkShiftResponse) {
                viewModelScope.launch(Dispatchers.IO) {
            val state = getState()
            val txnRequest = state.request.transactionRequest()
            val txnResponse = txnRequest.makeResponse()
            if (apiResponse.result.success) {
                txnResponse.batchNumber = (System.currentTimeMillis() / 1000).toString()
                txnResponse.amount = apiResponse.result.transactionTotalAmount?.toString()
                apiResponse.result.transactionListContent?.let { rt ->
                    txnResponse.batchReceipt = convertReceipt(rt)
                }
                txnResponse.setOK()
            } else {
                txnResponse.setFailed(apiResponse.result.message, "Error")
            }
            txnResponse.sendResultWith(
                state.request.makeResponse(this)
            )
        }
    }*/
    private suspend fun startShiftClose(batchCloseUseCase: BatchCloseUseCase) {
        val request = ApiWorkShiftRequest(
            closeShift = true,
            listType = ApiWorkShiftRequest.ListType.terminal,
            pinpad = "*",
            printTransactionList = false,
            showTransactionList = false,
            returnTransactionList = true,
            transactionListContentType = ApiWorkShiftRequest.TransactionListContentType.text
        )
        batchCloseUseCase.getFinalizeProgress(request)

        //finalizeShiftClose() //TODO
    }

    private suspend fun startTransaction(
        transaction: TransactionRequest,
        usecase: TransactionUseCase
    ) {
        //startTransaction(transaction)
        val opType = when (transaction.transactionType) {
            TransactionType.Sale -> ApiTxnStartRequest.OpType.sale
            TransactionType.NegativeSale -> ApiTxnStartRequest.OpType.refund
            TransactionType.Refund -> ApiTxnStartRequest.OpType.refund
            TransactionType.AdjustTips -> ApiTxnStartRequest.OpType.sale
            TransactionType.VoidTransaction -> ApiTxnStartRequest.OpType.cancel
            else -> throw NotImplementedException("${transaction.transactionType} not supported")
        }
        val txnStart = ApiTxnStartRequest(
            opType = opType,
            requestedAmount = transaction.totalAmount().toCents(),
            transactionReference = "HioPos#${transaction.transactionId}",
            executeOptions = ExecuteOptions(
                ExecuteOptions.Method.custom,
                userData = "MSG#HioPos"
            ),
            operationDetails = transaction.transactionData?.toLong()
                ?.let { ApiTxnStartRequestOperationDetails(it) },
            createReceipt = true,
            receiptType = ApiTxnStartRequest.ReceiptType.text,
            pinpad = "*"
        )

        usecase.transactionProccess(txnStart).also {
            _hioPosIntentResult.value = it.hioPosResponseIntent
            _UiMessage.value = usecase.uiMessage.value
        // We use the mutableStateFlow, which is passed to useCase in order to update the diferents repsonse which getTransactionPoll will be returning.
        }
    }
/*
    private fun convertReceipt(receiptText: String?): String? {
        if (receiptText.isNullOrBlank()) return null
        val reader = StringReader(receiptText)
        val xml = XmlBuilder("Receipt")
        reader.forEachLine { line ->
            xml.root.element("ReceiptLine") { n ->
                n.attribs["type"] = "TEXT"
                n.textElement("Text", line)
            }
        }
        return xml.toString()
    }
 */
}

Regarding some simple flow which don't make use of an API call, to simmplify the question, we have this, we set the version of the app:

@ActivityRetainedScoped
class GetVersionUseCase @Inject constructor(
    val request: HioPosRequest
) {
    fun getVersion():HioPosResponseIntent {
        val response = request.makeResponse()
        response.setString("Version", APK_VERSION)
        return response
    }

}

As you see it just set an String resource with the version in the response.

So, in my testing functin how should I call to the generate onActivityResoult methd Which is going to give me the response of my app, on this flow?

    @RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        assertEquals("es.paytef.hioposconnector", appContext.packageName)
    }
    @Test
    fun testVersionCase(){
        //todo: Send Intent: ForResult to MainActivity
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        val intent = Intent(appContext, MainActivity::class.java)
        intent.action = "icg.actions.electronicpayment.paytef.GET_VERSION"
        //val _activity = InstrumentationRegistry.getInstrumentation().newActivity(ClassLoader.getSystemClassLoader(),MainActivity::class.java.simpleName,intent)
        val activity = ActivityScenario.launchActivityForResult<MainActivity>(intent)
        Log.d("result", "result: ${activity.result}")
        assertEquals(activity.result.resultData, intent)
    }
}

I'm getting this error in testVersionCase():

java.lang.AssertionError: expected:<Intent { act=icg.actions.electronicpayment.paytef.GET_VERSION (has extras) }> but was:<Intent { act=icg.actions.electronicpayment.paytef.GET_VERSION cmp=es.paytef.hioposconnector/.MainActivity }>

Any idea??

Thanks in advance!

Manuel Lucas
  • 636
  • 6
  • 17

0 Answers0