Xctest how to know test cases pass/fail before suit finished so that i can update test cycle when ever test finished - xctest

override func setUp() {
super.setUp()
continueAfterFailure = false
XCTestObservationCenter.shared.addTestObserver(observer)
XCUIApplication().launch()
let observer = Observer() // your created observer class
let observationCenter = XCTestObservationCenter.shared
observationCenter.addTestObserver(observer)
}
override func tearDown() {
super.tearDown()
And below is the observer class
but nothing is prinitng
i want the test case id and status of each test case finished running so that i will update status in Jira or Zephyr
class Observer: NSObject, XCTestObservation {
var failedTC = 0
func testBundleWillStart(_ testBundle: Bundle) {
print("test Bundle Started")
}
func testBundleDidFinish(_ testBundle: Bundle) {
print("test Bundle Finished")
}
func testSuiteWillStart(_ testSuite: XCTestSuite) {
print("test Suite Started")
}
func testSuiteDidFinish(_ testSuite: XCTestSuite) {
print("test Bundle Ended")
}
private func testSuite(_ testSuite: XCTestSuite, didFailWithDescription description: String, inFile filePath: String?, atLine lineNumber: UInt) {
print("test Suit failed information" + description)
}
func testCaseWillStart(_ testCase: XCTestCase) {
print("test case Started")
}
func testCaseDidFinish(_ testCase: XCTestCase) {
print("test case Finished")
}
private func testCase(_ testCase: XCTestCase, didFailWithDescription description: String, inFile filePath: String?, atLine lineNumber: UInt) {
print("test case failed message" + description)
var tmpMsgArr = description.components(separatedBy: ".---")
let testcaseID = tmpMsgArr[1]
print("------" + testcaseID)
// yourmethodThatwillbeCalledWhenTCFail() // implement this method that you want to execute
failedTC += 1
}
}

Related

Kotlin update textview every 1 second

i want to update text of progressbar and i have this error
i tried but not work anything
some advice?
why i cant update text ?
Thanks!
2021-10-26 22:31:00.555 6192-6227/? E/AndroidRuntime: FATAL EXCEPTION: Timer-0
Process: com.example.myapplication, PID: 6192
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8798)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1606)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3593)
at android.view.View.requestLayout(View.java:25390)
at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3593)
at android.view.View.requestLayout(View.java:25390)
at android.widget.TextView.checkForRelayout(TextView.java:9719)
at android.widget.TextView.setText(TextView.java:6311)
at android.widget.TextView.setText(TextView.java:6139)
at android.widget.TextView.setText(TextView.java:6091)
at com.example.myapplication.MainActivity$onCreate$1$1$onResponse$2$1.run(MainActivity.kt:84)
at java.util.TimerThread.mainLoop(Timer.java:562)
at java.util.TimerThread.run(Timer.java:512)
class MainActivity() : AppCompatActivity() {
private lateinit var textViewTur: TextView
companion object myCompanion {
var test8: Int = 0
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textViewTur = findViewById(R.id.textTur)
val url = "http://blynk-cloud.com/b3Uq-vB64Hz1D_X3AJ506Q9OwmQLwha7/get/V5"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
with(client) {
newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
println("faillllllllllllllllllllllll")
}
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
var test = body?.replace("[", "")?.replace("]", "")?.replace("\"", "")
val tboiler = findViewById<CircularProgressBar>(R.id.PB_Tb)
tboiler.apply {
val timer = Timer()
timer.schedule(
object : TimerTask() {
override fun run() {
progress = test?.toFloat()!!
myCompanion.test8 = progress.toInt()
textViewTur.text = progress.toString()
}
},
0,
2000
)
}
}
})
}
}
The Timer class runs its code on a new thread, but you can only update UI elements like TextView from the main thread.
You can use postDelayed on a view to tell it to run code on the main thread after some delay, for example:
tBoiler.apply {
postDelayed(2000L, {
progress = test?.toFloat()!!
myCompanion.test8 = progress.toInt()
textViewTur.text = progress.toString()
})
}
This will not run the code repeatedly. If you want to do that, you could move the delayed action into a separate function that can recursively call itself, and call the function to start the loop. You need to store the runnable in a property if you want to be able to stop the loop.
private val progressRunnable = Runnable {
tBoiler.apply {
progress = test?.toFloat()!!
myCompanion.test8 = progress.toInt()
textViewTur.text = progress.toString()
loopProgress()
}
}
private fun loopProgress() {
tBoiler.postDelayed(2000L, progressRunnable)
}
private fun stopLoopProgress {
tBoiler.removeCallbacks(progressRunnable)
}
Alternatively, your code could be simplified with a coroutine:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textViewTur = findViewById(R.id.textTur)
lifecycleScope.launch {
val url = "http://blynk-cloud.com/b3Uq-vB64Hz1D_X3AJ506Q9OwmQLwha7/get/V5"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
val response = try {
client.newCall(request)
} catch (e: IOException) {
println("faillllllllllllllllllllllll")
return#launch
}
val test = response.body?.string()?.filter { it.isDigit() || it == '.' }
.toFloatOrNull()
if (test == null) {
println("body cannot be parsed as Float: ${response.body}")
return#launch
}
val tboiler = findViewById<CircularProgressBar>(R.id.PB_Tb)
while (true) {
delay(2000L)
tboiler.apply {
progress = test
myCompanion.test8 = progress.toInt()
textViewTur.text = progress.toString()
}
}
}
}
// You can put this in a file for http utility functions
public suspend fun Call.await(): Response = suspendCancellableCoroutine { cont ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
cont.resume(response)
}
override fun onFailure(call: Call, e: IOException) {
cont.resumeWithException(e)
}
})
cont.invokeOnCancellation {
runCatching { cancel() }
}
}

How i can use Flow on swift Kotlin multiplatform?

I am creating my first kotlin multiplataform project, and i'm having some difficulty to use the kotlin flow on swift. I created the models, server data requests as common file using kotlin flow and ktor, the the view model and ui layers i am creating as native. So, i have no experience with swift development, and beyond that i' having a lot of trouble to use flow on swift view model. Searching for an answer to my problem i found a class described as CommonFlow, which aims to be used as a common code for both languages(kotlin, swift, but i'm having an error that gives me little or no clue as to why it happens or, probably, it's just my lack of dominion with xcode and swift programming:
So this is the code part that xcode points the error:
Obs: sorry about the image i thought that maybe this time it would be more descriptive
And this its all i got from the error
Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee5928ff8)
MY Ios ViewModel:
class ProfileViewModel: ObservableObject {
private let repository: ProfileRepository
init(repository: ProfileRepository) {
self.repository = repository
}
#Published var authentication: Authetication = Authetication.unauthenticated(false)
#Published var TokenResponse: ResponseDTO<Token>? = nil
#Published var loading: Bool = false
func authenticate(email: String, password: String) {
DispatchQueue.main.async {
if(self.isValidForm(email: email, password: password)){
self.repository.getTokenCFlow(email: email, password: password).watch{ response in
switch response?.status {
case StatusDTO.success:
self.loading = false
let token: String = response!.data!.accessToken!
SecretStorage().saveToken(token: token)
self.authentication = Authetication.authenticated
break;
case StatusDTO.loading:
self.loading = true
break;
case StatusDTO.error:
print("Ninja request error \(String(describing: response!.error!))}")
break;
default:
break
}
}
}
}
}
private func isValidForm(email: String, password: String) -> Bool {
var invalidFields = [Pair]()
if(!isValidEmail(email)){
invalidFields.append(Pair(first:"email invalido",second: "email invalido"))
}
if(password.isEmpty) {
invalidFields.append(Pair(first:"senha invalida",second: "senha invalida"))
}
if(!invalidFields.isEmpty){
self.authentication = Authetication.invalidAuthentication(invalidFields)
return false
}
return true
}
private func isValidEmail(_ email: String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPred = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailPred.evaluate(with: email)
}
}
class Pair {
let first: String
let second: String
init(first:String, second: String) {
self.first = first
self.second = second
}
}
enum Authetication {
case invalidAuthentication([Pair])
case authenticated
case persistentAuthentication
case unauthenticated(Bool)
case authenticationFailed(String)
}
The repository methods:
override fun getToken(email: String, password: String): Flow<ResponseDTO<Token>> = flow {
emit(ResponseDTO.loading<Token>())
try {
val result = api.getToken(GetTokenBody(email, password))
emit(ResponseDTO.success(result))
} catch (e: Exception) {
emit(ResponseDTO.error<Token>(e))
}
}
#InternalCoroutinesApi
override fun getTokenCFlow(email: String, password: String): CFlow<ResponseDTO<Token>> {
return wrapSwift(getToken(email, password))
}
The Class CFLOW:
#InternalCoroutinesApi
class CFlow<T>(private val origin: Flow<T>): Flow<T> by origin {
fun watch(block: (T) -> Unit): Closeable {
val job = Job()
onEach {
block(it)
}.launchIn(CoroutineScope(Dispatchers.Main + job))
return object: Closeable {
override fun close() {
job.cancel()
}
}
}
}
#FlowPreview
#ExperimentalCoroutinesApi
#InternalCoroutinesApi
fun <T> ConflatedBroadcastChannel<T>.wrap(): CFlow<T> = CFlow(asFlow())
#InternalCoroutinesApi
fun <T> Flow<T>.wrap(): CFlow<T> = CFlow(this)
#InternalCoroutinesApi
fun <T> wrapSwift(flow: Flow<T>): CFlow<T> = CFlow(flow)
There is an example of using flows in KampKit
https://github.com/touchlab/KaMPKit
I will paste an excerpt from NativeViewModel (iOS)
class NativeViewModel(
private val onLoading: () -> Unit,
private val onSuccess: (ItemDataSummary) -> Unit,
private val onError: (String) -> Unit,
private val onEmpty: () -> Unit
) : KoinComponent {
private val log: Kermit by inject { parametersOf("BreedModel") }
private val scope = MainScope(Dispatchers.Main, log)
private val breedModel: BreedModel = BreedModel()
private val _breedStateFlow: MutableStateFlow<DataState<ItemDataSummary>> = MutableStateFlow(
DataState.Loading
)
init {
ensureNeverFrozen()
observeBreeds()
}
#OptIn(FlowPreview::class)
fun observeBreeds() {
scope.launch {
log.v { "getBreeds: Collecting Things" }
flowOf(
breedModel.refreshBreedsIfStale(true),
breedModel.getBreedsFromCache()
).flattenMerge().collect { dataState ->
_breedStateFlow.value = dataState
}
}
This ViewModel is consumed in swift like this:
lazy var adapter: NativeViewModel = NativeViewModel(
onLoading: { /* Loading spinner is shown automatically on iOS */
[weak self] in
guard let self = self else { return }
if (!(self.refreshControl.isRefreshing)) {
self.refreshControl.beginRefreshing()
}
},
onSuccess: {
[weak self] summary in self?.viewUpdateSuccess(for: summary)
self?.refreshControl.endRefreshing()
},
onError: { [weak self] error in self?.errorUpdate(for: error)
self?.refreshControl.endRefreshing()
},
onEmpty: { /* Show "No doggos found!" message */
[weak self] in self?.refreshControl.endRefreshing()
}
)
In short, the flow is kept wrapped in kotlin mp land and leveraged in iOS by using traditional callback interfaces.

WorkManager observe retrofit progress

I am uploading the file using Kotlin workmanager. In CoroutineWorkmanager, I do file upload with a suspend upload function. I want to observe retrofit progress and show it on ui. I can see the retrofit progress state but I cannot observe it in workManager.
My request body class where I can see the retrofit progress state :
class ProgressRequestBody : RequestBody {
val mFile: File
val ignoreFirstNumberOfWriteToCalls : Int
constructor(mFile: File) : super(){
this.mFile = mFile
ignoreFirstNumberOfWriteToCalls = 0
}
constructor(mFile: File, ignoreFirstNumberOfWriteToCalls : Int) : super(){
this.mFile = mFile
this.ignoreFirstNumberOfWriteToCalls = ignoreFirstNumberOfWriteToCalls
}
var numWriteToCalls = 0
private val _shared = MutableStateFlow<Float>(0F)
val shared : StateFlow<Float> = _shared
override fun contentType(): MediaType? {
return "image/*".toMediaTypeOrNull()
}
#Throws(IOException::class)
override fun contentLength(): Long {
return mFile.length()
}
#Throws(IOException::class)
override fun writeTo(sink: BufferedSink) {
numWriteToCalls++
val fileLength = mFile.length()
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
val `in` = FileInputStream(mFile)
var uploaded: Long = 0
try {
var read: Int
var lastProgressPercentUpdate = 0.0f
read = `in`.read(buffer)
while (read != -1) {
uploaded += read.toLong()
sink.write(buffer, 0, read)
read = `in`.read(buffer)
if (numWriteToCalls > ignoreFirstNumberOfWriteToCalls ) {
val progress = (uploaded.toFloat() / fileLength.toFloat()) * 100f
if (progress - lastProgressPercentUpdate > 1 || progress == 100f) {
_shared.value = progress
Log.d("progress", "${shared.value}")
lastProgressPercentUpdate = progress
}
}
}
} finally {
`in`.close()
}
}
companion object {
private val DEFAULT_BUFFER_SIZE = 2048
}
}
The worker I uploaded the file to:
class UploadWorker #WorkerInject constructor(
private val repository: Repository,
#Assisted context: Context,
#Assisted params: WorkerParameters
): CoroutineWorker(context, params) {
private lateinit var result: UploadResult
#ObsoleteCoroutinesApi
#OptIn(ExperimentalCoroutinesApi::class)
#SuppressLint("RestrictedApi")
override suspend fun doWork(): Result {
return try{
val requestBody = ProgressRequestBody(File(fileUri!!.toUri().path))
val multipartBody = prepareBody(fileUri!!.toUri(), photoPart)
progressState(requestBody)
upload(multipartBody)
Result.Success()
}catch(e :Exception){
Result.failure()
}
}
private fun prepareBody( ): MultipartBody.Part {
return MultipartBody.Part.createFormData("photo", "photo", "image/*")
}
suspend fun upload(
multipartBody: MultipartBody.Part,
) {
repository.uploadPhotos(
multipartBody
).collect{ result ->
if (result is Result.Success) {
this.result = result.data
}
}
}
private suspend fun progressState(photoPart: ProgressRequestBody) {
coroutineScope {
launch {
photoPart.shared.collect{
setProgress(workDataOf(PROGRESS to it))
}
}
}
}
}
While this way I can't run the worker. I am getting the following error from the worker:
java.util.concurrent.CancellationException: Task was cancelled.
at androidx.work.impl.utils.futures.AbstractFuture.cancellationExceptionWithCause(AbstractFuture.java:1184)
at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:514)

How to close the channel after all producer coroutines are done?

Consider the following code:
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
val channel = Channel<String>()
launch {
channel.send("A1")
channel.send("A2")
log("A done")
}
launch {
channel.send("B1")
log("B done")
}
launch {
for (x in channel) {
log(x)
}
}
}
fun log(message: Any?) {
println("[${Thread.currentThread().name}] $message")
}
The original version has the receiver coroutine like that:
launch {
repeat(3) {
val x = channel.receive()
log(x)
}
}
It expects only 3 messages in the channel. If I change it to the first version then I need to close the channel after all producer coroutines are done. How can I do that?
A possible solution is to create a job that will wait for all channel.send() to finish, and call channel.close() in the invokeOnCompletion of this job:
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
val channel = Channel<String>()
launch {
launch {
channel.send("A1")
channel.send("A2")
log("A done")
}
launch {
channel.send("B1")
log("B done")
}
}.invokeOnCompletion {
channel.close()
}
launch {
for (x in channel) {
log(x)
}
}
}
fun log(message: Any?) {
println("[${Thread.currentThread().name}] $message")
}

Cannot test callbacks with mockk: invoke(any())) was not called

Given
typealias MyCallback = (s: String) -> Unit
object Hello {
fun main() {
blah { print(it) }
}
fun blah(cb: MyCallback) {
cb.invoke("hi")
}
}
Or
interface MyCallback {
fun invoke (s: String) {}
}
object Hello {
fun main() {
blah(object : MyCallback {
override fun invoke(s: String) {
print(s)
}
})
}
fun blah(cb: MyCallback) {
cb.invoke("hi")
}
}
With both I get the above error (Verification failed: call 1 of 1: MyCallback(#2).invoke(any())) was not called) when doing
#Test
fun testInvoke() {
val mock = mockk<Hello>(relaxed = true)
val cb = mockk<MyCallback>()
mock.blah(cb)
verify { cb.invoke(any()) }
}
how to fix it?
This worked for me. The Hello object doesn't need to be mocked since it is the class being tested. By mocking it, the test was only recording invocations of blah() rather than actually executing them.
Using spyk rather than mockk allows the MyCallback type to be constructed allowing the invoke() function to be defined. So maybe that's more of a workaround than an explanation of why mockk doesn't seem to retain that type info.
typealias MyCallback = (s: String) -> Unit
object Hello {
fun main() {
blah { print(it) }
}
fun blah(cb: MyCallback) {
cb.invoke("hi")
}
}
class MockKTest {
#Test
fun testInvoke() {
val mock = spyk<Hello>()
val cb = mockk<MyCallback>(relaxed = true)
mock.blah(cb) // or just do Hello.blah(cb)
verify { cb.invoke(any()) }
}
}