Kotlin equivalent of Swift Expectations/Promises - swift

I am trying to write some UnitTests for my native mobile applications, but have ran into a roadblock in my Android Tests. Specifically, I am struggling to find an example of Kotlin's version of Swift's Expectations/Promises..
I've found examples of Kotlin Promises, but they seem to be way more complicated than needed...
For example, here is a test for the login API function in my iOS project:
func testLogin() {
/// Prepare for login
if CURRENT_USER != nil {
logout()
}
/// Login
let promise = expectation(description: "User is logged in.")
// 1. Given
var isSuccess: Bool = false
// 2. When
User.login(username: maleUsername, password: malePassword, success: {
isSuccess = true
promise.fulfill()
}) { (_, agreeToTerms) in
XCTFail()
}
wait(for: [promise], timeout: maxTimeOut)
// 3. Then
XCTAssertNotNil(CURRENT_USER)
XCTAssertTrue(isSuccess)
/// Logout
logout()
}
This is pretty simple to me. I have an asynchronous method login that has two possible completion blocks: success and failure; and I need to wait for one of them to complete before evaluating. To do this, I create a promise before the call, then I fulfill the promise in the two completion blocks, and I wait for the promise to fulfill before running my assertions.
Now in Kotlin, I have a similar test:
private val loginFragment = LoginFragment()
#Test
fun loginTest() {
val username = ""
val password = ""
// TODO: Create Promise
loginFragment.loginViewModel
.login(username, password)
.observe(loginFragment, Observer {
loginFragment.activity?.onResult(it?.result,
onSuccess = {
// TODO: Fill Promise
},
onValidationError = {
// TODO: Fail Test
})
})
// TODO: Assertions
}
But I can't find an equivalent of swift's promises..
Does one exist in Kotlin? If not, how would I implement a version of my Swift's testLogin method in Kotlin?

You can use Kotlin coroutines, for example:
#Test
fun loginTest() {
val result = runBlocking {
val loginResult = login()
loginResult
}
if (result == "Success") {
// do your work when Success
} else {
// do your work when Error
}
}
suspend fun login(): String = suspendCoroutine { continuation ->
val username = ""
val password = ""
loginFragment.loginViewModel
.login(username, password)
.observe(loginFragment, Observer {
loginFragment.activity?.onResult(it?.result,
onSuccess = {
continuation.resume("Success")
},
onValidationError = {
continuation.resume("Error") // take a look for other methods, e.g. resumeWithException(exception)
})
})
}
To use coroutines you need to add next lines to app's build.gradle file dependencies:
final KOTLIN_COROUTINES_VERSION = '1.0.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$KOTLIN_COROUTINES_VERSION"
Hope it will help.

I found this Kotlin Promise lib closer to Swift Promises.
Using this library your test would like.
#Test
fun loginTest() {
val username = ""
val password = ""
Promise<Boolean, Exception> { promise ->
loginFragment.loginViewModel
.login(username, password)
.observe(loginFragment, Observer {
loginFragment.activity?.onResult(it?.result,
onSuccess = {
promise.resolve(true)
},
onValidationError = {
promise.reject(Exception("Login error"))
})
})
}.whenComplete {
when (it) {
is Promise.Result.Success -> {
assert(it.value)
}
}
}
}

I wrote in 20 minutes an implementation that looks like swift's expectation, but he is not sophisticated, use only for simple cases it does the work well.
typealias XCTestExceptionHandler = (KTTestException?) -> Unit
fun TestCase.expectation(description: String): KTTestExpectation {
return KTTestExpectation(description)
}
fun TestCase.waitFor(expectation: KTTestExpectation, timeout: Long, handler: XCTestExceptionHandler) {
expectation.handler = handler
Thread.sleep(timeout)
expectation.timedOut()
}
class KTTestExpectation(private val description: String) {
private var isFulfilled = false
var handler: XCTestExceptionHandler? = null
fun fulfill() {
Handler(Looper.getMainLooper()).postDelayed({
invokeHandlerWith(null)
}, 2)
}
fun timedOut() {
invokeHandlerWith(KTTestException("Timed out: $description"))
}
private fun invokeHandlerWith(error: KTTestException?) {
if (isFulfilled) return
isFulfilled = true
handler?.invoke(error)
error?.let { Assert.fail("Timed out: $description") }
}
}
class KTTestException(message:String): Exception(message)
Using:
fun testExpectation() {
var nb = 0
val expectation = expectation("Test")
MyAsyncFunc {
nb = 5
expectation.fulfill()
}
waitFor(expectation, 1000) { error ->
assertEquals(5, nb)
}
}
If anyone has the courage to convert the original code to Kotlin Here are the links:
https://github.com/apple/swift-corelibs-xctest/blob/main/Sources/XCTest/Public/Asynchronous/XCTWaiter.swift
https://github.com/apple/swift-corelibs-xctest/blob/main/Sources/XCTest/Public/Asynchronous/XCTestExpectation.swift

Related

Concurrently run async tasks with unnamed async let

With Swift concurrency, is it possible to have something almost like an 'unnamed' async let?
Here is an example. You have the following actor:
actor MyActor {
private var foo: Int = 0
private var bar: Int = 0
func setFoo(to value: Int) async {
foo = value
}
func setBar(to value: Int) async {
bar = value
}
func printResult() {
print("foo =", foo)
print("bar =", bar)
}
}
Now I want to set foo and bar using the given methods. Simple usage would look like the following:
let myActor = MyActor()
await myActor.setFoo(to: 5)
await myActor.setBar(to: 10)
await myActor.printResult()
However this code is sequentially run. For all intents and purposes, assume setFoo(to:) and setBar(to:) may be a long running task. You can also assume the methods are mutually exclusive (don't share variables & won't affect each other).
To make this code current, async let can be used. However, this just starts the tasks until they are awaited later on. In my example you'll notice I don't need the return value from these methods. All I need is that before printResult() is called, the previous tasks have completed.
I could come up with the following:
let myActor = MyActor()
async let tempFoo: Void = myActor.setFoo(to: 5)
async let tempBar: Void = myActor.setBar(to: 10)
let _ = await [tempFoo, tempBar]
await myActor.printResult()
Explicitly creating these tasks and then awaiting an array of them seems incorrect. Is this really the best way?
This can be achieved with a task group using withTaskGroup(of:returning:body:). The method calls are individual tasks, and then we await waitForAll() which continues when all tasks have completed.
Code:
await withTaskGroup(of: Void.self) { group in
let myActor = MyActor()
group.addTask {
await myActor.setFoo(to: 5)
}
group.addTask {
await myActor.setBar(to: 10)
}
await group.waitForAll()
await myActor.printResult()
}
I made your actor a class to allow concurrent execution of the two methods.
import Foundation
final class Jeep {
private var foo: Int = 0
private var bar: Int = 0
func setFoo(to value: Int) {
print("begin foo")
foo = value
sleep(1)
print("end foo \(value)")
}
func setBar(to value: Int) {
print("begin bar")
bar = value
sleep(2)
print("end bar \(bar)")
}
func printResult() {
print("printResult foo:\(foo), bar:\(bar)")
}
}
let jeep = Jeep()
let blocks = [
{ jeep.setFoo(to: 1) },
{ jeep.setBar(to: 2) },
]
// ...WORK
RunLoop.current.run(mode: RunLoop.Mode.default, before: NSDate(timeIntervalSinceNow: 5) as Date)
Replace WORK with one of these:
// no concurrency, ordered execution
for block in blocks {
block()
}
jeep.printResult()
// concurrency, unordered execution, tasks created upfront programmatically
Task {
async let foo: Void = blocks[0]()
async let bar: Void = blocks[1]()
await [foo, bar]
jeep.printResult()
}
// concurrency, unordered execution, tasks created upfront, but started by the system (I think)
Task {
await withTaskGroup(of: Void.self) { group in
for block in blocks {
group.addTask { block() }
}
}
// when the initialization closure exits, all child tasks are awaited implicitly
jeep.printResult()
}
// concurrency, unordered execution, awaited in order
Task {
let tasks = blocks.map { block in
Task { block() }
}
for task in tasks {
await task.value
}
jeep.printResult()
}
// tasks created upfront, all tasks start concurrently, produce result as soon as they finish
let stream = AsyncStream<Void> { continuation in
Task {
let tasks = blocks.map { block in
Task { block() }
}
for task in tasks {
continuation.yield(await task.value)
}
continuation.finish()
}
}
Task {
// now waiting for all values, bad use of a stream, I know
for await value in stream {
print(value as Any)
}
jeep.printResult()
}

How I can update my adapter for RecyclerView after change my LiveData?

I created a fragment that in the onActivityCreated method fetches Firebase data by limiting the query to a calendar date. Then I place Observers on my LiveData that are inside my ViewModel and that will deliver the list to my Adapter.
If I add, remove or update items in the same list, the changes are sent to firebase and the adapter reflects them on the screen. It works ok.
But, I am trying to develop a filter button, which will basically change the deadline date for the Firebase query. When I select a particular filter, the viewModel needs to retrieve the data from Firebase limited to the filter date. This generates a new list, having a different size from the previous one.
However, when the query occurs, the Adapter's getItemCount() method stores the size of the last list. This fact confuses the Adapter and the functions notifyItemInserted and notifyItemRemoved end up making confusing animations on the screen after changing the filter. I dont know whats is wrong.
How can I correctly observes LiveData and tell the adapter? Am I making a mistake in the MVVM architecture or forgetting some function?
My Fragment:
class HistoryFragment : Fragment(), OnItemMenuRecyclerViewClickListener {
private lateinit var mSecurityPreferences: SecurityPreferences
private lateinit var viewModel: BalancesViewModel
private lateinit var adapter: BalancesAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
setHasOptionsMenu(true)
viewModel = ViewModelProvider(this).get(BalancesViewModel::class.java)
adapter = BalancesAdapter(requireContext())
mSecurityPreferences = SecurityPreferences(requireContext())
return inflater.inflate(R.layout.fragment_history, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupFilter()
//Setup adapter
adapter.listenerMenu = this
recycler_view_history.adapter = adapter
//Fetch data based in filter by date
viewModel.fetchBalances(mSecurityPreferences.getStoredLong(FILTER_DATE))
// Put logic to listen RealTimeUpdates
viewModel.getRealTimeUpdates(mSecurityPreferences.getStoredLong(FILTER_DATE))
viewModel.balances.observe(viewLifecycleOwner, Observer {
adapter.setBalances(it)
})
viewModel.balance.observe(viewLifecycleOwner, Observer {
adapter.addBalance(it)
})
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.history_menu_filter, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.item_menu_filter_this_month -> {
updateFilter(THIS_MONTH)
}
R.id.item_menu_filter_two_months -> {
updateFilter(TWO_MONTHS)
}
R.id.item_menu_filter_last_six_months -> {
updateFilter(LAST_SIX_MONTHS)
}
R.id.item_menu_filter_all -> {
updateFilter(ALL_MONTHS)
}
}
return super.onOptionsItemSelected(item)
}
private fun setupFilter() {
var filterOption = mSecurityPreferences.getStoredLong(FILTER_DATE)
if (filterOption == 0L){
filterOption = HandleDate.getLongToFilter(LAST_SIX_MONTHS)
mSecurityPreferences.storeLong(FILTER_DATE, filterOption)
}
}
private fun updateFilter(filterOption: Int){
val newFilterOption = HandleDate.getLongToFilter(filterOption)
mSecurityPreferences.storeLong(FILTER_DATE, newFilterOption)
updateUI()
}
private fun updateUI(){
viewModel.fetchBalances(mSecurityPreferences.getStoredLong(FILTER_DATE))
viewModel.getRealTimeUpdates(mSecurityPreferences.getStoredLong(FILTER_DATE))
}
}
My ViewModel:
class BalancesViewModel : ViewModel() {
private val userReference = FirebaseAuth.getInstance().currentUser!!.uid
private val dbUserReference = FirebaseDatabase.getInstance().getReference(userReference)
private val _balances = MutableLiveData<List<Balance>>()
val balances: LiveData<List<Balance>>
get() = _balances
private val _balance = MutableLiveData<Balance>()
val balance: LiveData<Balance>
get() = _balance
private val _result = MutableLiveData<Exception?>()
val result: LiveData<Exception?>
get() = _result
fun addBalance(balance: Balance) {
balance.id = dbUserReference.push().key
dbUserReference.child(NODE_BALANCES).child(balance.id!!).setValue(balance)
.addOnCompleteListener {
if (it.isSuccessful) {
_result.value = null
} else {
_result.value = it.exception
}
}
}
private val childEventListener = object : ChildEventListener {
override fun onCancelled(error: DatabaseError) {
}
override fun onChildMoved(snapshot: DataSnapshot, p1: String?) {
}
override fun onChildChanged(snapshot: DataSnapshot, p1: String?) {
val balance = snapshot.getValue(Balance::class.java)
balance?.id = snapshot.key
_balance.value = balance
}
override fun onChildAdded(snapshot: DataSnapshot, p1: String?) {
val balance = snapshot.getValue(Balance::class.java)
balance?.id = snapshot.key
_balance.value = balance
}
override fun onChildRemoved(snapshot: DataSnapshot) {
val balance = snapshot.getValue(Balance::class.java)
balance?.id = snapshot.key
balance?.isDeleted = true
_balance.value = balance
}
}
fun getRealTimeUpdates(longLimitDate: Long) {
dbUserReference.child(NODE_BALANCES).orderByChild(COLUMN_DATE_MILLI)
.startAt(longLimitDate.toDouble()).addChildEventListener(childEventListener)
}
fun fetchBalances(longLimitDate: Long) {
dbUserReference.child(NODE_BALANCES).orderByChild(COLUMN_DATE_MILLI)
.startAt(longLimitDate.toDouble())
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(error: DatabaseError) {}
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
val listBalances = mutableListOf<Balance>()
for (balanceSnapshot in (snapshot.children)) {
val balance = balanceSnapshot.getValue(Balance::class.java)
balance?.id = balanceSnapshot.key
balance?.let { listBalances.add(it) }
}
listBalances.sortByDescending { it.dateMilli }
_balances.value = listBalances
}
}
})
}
fun updateBalance(balance: Balance) {
dbUserReference.child(NODE_BALANCES).child(balance.id!!).setValue(balance)
.addOnCompleteListener {
if (it.isSuccessful) {
_result.value = null
} else {
_result.value = it.exception
}
}
}
fun deleteBalance(balance: Balance) {
dbUserReference.child(NODE_BALANCES).child(balance.id!!).setValue(null)
.addOnCompleteListener {
if (it.isSuccessful) {
_result.value = null
} else {
_result.value = it.exception
}
}
}
My Adapter:
class BalancesAdapter(private val context: Context) :
RecyclerView.Adapter<BalancesAdapter.BalanceViewModel>() {
private var balances = mutableListOf<Balance>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
BalanceViewModel(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_recyclerview_balance, parent, false)
)
override fun getItemCount() = balances.size
override fun onBindViewHolder(holder: BalanceViewModel, position: Int) {
holder.view.text_view_value_balance_item.text = balances[position].value
holder.view.text_view_date_item.text = balances[position].date
}
fun setBalances(balances: List<Balance>) {
this.balances = balances as MutableList<Balance>
notifyDataSetChanged()
}
fun addBalance(balance: Balance) {
val index = balances.indexOf(balance)
if (!balances.contains(balance)) {
balances.add(balance)
notifyItemInserted(index)
} else {
if (balance.isDeleted) {
balances.removeAt(index)
notifyItemRemoved(index)
} else {
balances[index] = balance
}
}
notifyItemRangeChanged(index, itemCount)
}
class BalanceViewModel(val view: View) : RecyclerView.ViewHolder(view)
}
Tnks for your attention.
Okay, it's been 4 days since I asked this question and after feeling a little frustrated with the project I come back here on StackOverFlow to post my own answer.
The problematic issue within the code I showed is in my Adapter's addBalance method.
When I created the Balance data model, I set the isDeleted attribute to identify that it was deleted. Upon entering Firebase it receives a NULL value and therefore it ceases to exist.
Then, as I have two listeners (one defined in the addListenerForSingleValueEvent method and the other defined in the addChildEventListener method), one ends up triggering the other when there is a change in the Firebase data, but I don't want to go into detail on that issue. The fact is that I checked that my addBalance method was being called after I deleted an object, causing that object to be inserted back into the Adapter's data list, even before the removal operation ended in Firebase.
So I changed the logic of my method to make sure that my object was deleted and only included it in my Adapter list after checking the isDeleted attribute.
fun dealWithBalance(balance: Balance){
val index = balances.indexOf(balance)
if(balance.isDeleted && balances.contains(balance)){
balances.removeAt(index)
notifyItemRemoved(index)
} else if(!balance.isDeleted && !balances.contains(balance)){
balances.add(balance)
} else if(index >= 0){
balances[index] = balance
notifyItemChanged(index)
}
}
I renamed addBalance to dealWithBalance...

PromiseKit with optional promises

I am currently investigating if I should integrate PromiseKit in an existing project.
My main issue is that I need to implement a business logic that can call up to 5 web-services. Some of them are called depending on the result of previous ones.
My current architecture is based on breaking up the code in several functions with closures that call each other.
I am trying to find out if I could write an easier to manage code with PromiseKit (or anything else).
Here is some pseudo-code of what I need done:
// if true, the phone validation is skipped
let forceRequest = false
// true if a 3rd party web-service has checked the phone number
let isVerified = true
// true if the 3rd party checked the phone number and it is valid
var isValid = false
if !isVerified {
// update value from 3rd party web-service
isValid = isValidPhoneNumberPromise()
}
// If the phone no is invalid stop execution (unless forced)
if !isValid && !force {
throw MyError.error1
}
// web request to create order
createOrderPromise()
// if we have a valid phone number, first send an SMS, then update log
if isValid {
sendSmsPromise()
updateLogPromise()
}
Based on totiG's answer, I came with the following variation:
var isValid = isValid
firstly
{
return Controller.verify(isVerified: isVerified, isValid: isValid)
}
.then { _isValid -> Promise<Int> in
isValid = _isValid
return Controller.createOrder()
}
.then
{ _ -> Promise<Bool> in
if isValid {
return Controller.isSendSms()
}
return Promise (value: true)
}
.then
{ _ -> Promise<Bool> in
if isValid {
return Controller.updateLog()
}
return Promise (value: true)
}
.catch
{ error in
print (error)
}
Yes you can do this using PromiseKit. I have written a basic example showing what you might need. Remember that you would throw an Error when a step fails and handle those failures in the catch block. In my example the verify step passes, but if isValidPhoneNumber was called it would stop the other steps from running. Where I have put the Promise(value: ) you would put your actual web service calls. If the final step to update log is always required to run, you could put this in a .always
enum Errors: Error {
case invalidPhone
case orderFailed
}
func orderPromise() {
firstly {
self.verify(isVerified: false, force: true)
}.then { _ in
self.createOrder()
}.then { orderNumber in
self.sendSms(orderNumber: orderNumber)
}.then { smsSent in
self.updateLog(smsSent: smsSent)
}.catch { error in
//Do something with the error
}
}
private func verify(isVerified: Bool, force: Bool) -> Promise<Bool> {
if isVerified || force {
return Promise(value: true)
}
return isValidPhoneNumber()
}
private func isValidPhoneNumber() -> Promise<Bool> {
return Promise(error: Errors.invalidPhone) //Assume that this fails, then catch in 'orderPromise' will be run
}
private func createOrder() -> Promise<String> {
//Assume an order number is being passed back to use in the message
return Promise(value: "Order100")
}
private func sendSms(orderNumber: String) -> Promise<Bool> {
return Promise(value: true)
}
private func updateLog(smsSent: Bool) -> Promise<Bool> {
return Promise(value: true)
}

onNext not getting called unit testing RxCocoa Driver

I'm attempting to write a unit test for Driver from RxCocoa library. Here's my simplified implementation code:
struct LoginViewModel {
var username: Driver<String?>!
var password: Driver<String?>!
var loginTaps: Driver<Void>!
func login() -> Driver<LoginResult> {
let credentials = Driver.combineLatest(username, password) { ($0, $1) }
let latestCredentials = loginTaps.withLatestFrom(credentials)
return latestCredentials.flatMapLatest { (username, password) in
.just(.success)
}
}
}
And here's the Quick/Nimble unit test I'm attempting to pass:
let disposeBag = DisposeBag()
var capturedLoginResult = LoginResult.failed
loginViewModel.username = Driver.just("some username")
loginViewModel.password = Driver.just("some password")
loginViewModel.loginTaps = Driver.just()
loginViewModel.login().drive(onNext: { loginResult in
capturedLoginResult = loginResult
}).addDisposableTo(disposeBag)
expect(capturedLoginResult == .success)
Above expect says that capturedLoginResult is still .failed. It appears as though element from return latestCredentials.flatMapLatest { (username, password) in .just(.success) } is not getting received by the .drive(onNext: ) in the test.
If the implementation of login is just:
func login() -> Driver<LoginResult> {
return .just(.success)
}
The test passes.
Any thoughts on what's happening here? Thanks!
I don't know exactly where in Rx's source, but my guess is that an operator you are using is switching scheduler. Because of this, the subscription made with drive(onNext:) is not trigger immediately.
RxSwift provides a good API for testing our observables, through the RxTest package. You could rewrite your tests to take advantage of it.
let scheduler = TestScheduler(initialClock: 0)
let username = scheduler.createHotObservable([next(220, "username"), completed(20)])
let password = scheduler.createHotObservable([next(230, "p4ssw0rd"), completed(20)])
let loginTaps = scheduler.createHotObservable([next(240), completed(20)])
let recordObserver = scheduler.start(300) { () -> Observable<LoginResult> in
let loginViewModel = LoginViewModel()
loginViewModel.username = username.asDriver(onErrorJustReturn: "")
loginViewModel.password = username.asDriver(onErrorJustReturn: "")
loginViewModel.loginTaps = loginTaps.asDriver(onErrorJustReturn: ())
return loginViewModel.login().asObservable()
}
let expectedEvents: [Recorded<Event<LoginResult>>] = [
next(240, Login.success)
]
expect(recordObserver.events) == (expectedEvents)

How to check if an XCTestCase test has failed

Is it possible to check within a running test if any of its XCTAsserts have failed? I have a test with a few assertions in a row, and I want to add some code afterward to perform a specific action if any of them failed:
class testClass : XCTestCase
{
func testSomething()
{
let someComputedValue1 = func1()
let someComputedValue2 = func2()
XCTAssertLessThanOrEqual(someComputedValue1, 0.5)
XCTAssertLessThanOrEqual(someComputedValue2, 0.2)
if anyOfTheAboveAssertionsFailed {
performAction()
}
}
}
The part I'd like tips on is that anyOfTheAboveAssertionsFailed condition without duplicating the comparisons to the hard-coded values.
While using your own assertion methods solves the PO's issue, it is cumbersome if you need to use several XCAssert-methods.
Another approach is to override continueAfterFailure. If there is no failure the property will not be requested. If there is one, it will.
class MyTest: XCTest {
private var hasFailed = false
override var continueAfterFailure: Bool {
get {
hasFailed = true
return super.continueAfterFailure
}
set {
super.continueAfterFailure = newValue
}
}
override func tearDown() {
if hasFailed { performAction() }
hasFailed = false
}
}
You could of course write a new function...
func assertLessThanOrEqual(value: Double, limit: Double) -> Bool {
XCTAssertLessThanOrEqual(value, limit)
return value <= limit
}
And then write your tests like...
var allGood = true
allGood = allGood && assertLessThanOrEqual(someComputedValue1, 0.5)
allGood = allGood && assertLessThanOrEqual(someComputedValue2, 0.2)
if !allGood {
performAction()
}