Search code examples
stringkotlinunit-testingmvvmandroid-resources

Anyone know best practice for using String Resources when using MVVM? Kotlin


I am currently trying to write Unit tests for my code, and I am running into the problem of needing context for some of my classes (use-cases?).

Here is an example of the code:

In the class, I use the string resource to return a result.

class testClass {
    fun compareNumbers(numberOne: Int, numberTwo: Int, context: Context): String {
        var message = ""
        when {
            numberOne > numberTwo -> message = context.getString(R.string.numberOne)
            numberOne < numberTwo -> message = context.getString(R.string.numberTwo)
            numberOne == numberTwo -> message = context.getString(R.string.tie)
        }
        return message
    }

} 

In the view model:


fun declareWinner(context: Context) {
    val result = testClass.compareNumbers(numberOne, numberTwo, context)
    updateResult(result) //updates the result to a database
}

I thought this would have been a good way of separating the functions from one another and not making my view model messy. Plus I need functions like the compareNumbers in many VMs, so I thought it was more reusable. However, I am making a Unit test for this and it doesn't work as simply as I would like.

Is there a better way to do this or just try to make the context using mockito?


Solution

  • Your testClass shouldn't be aware of the Context. The role of a Use Case is to contain the business logic, but definitely not to be aware of any UI or Android Framework classes :[

    You could try to inject already localized strings into this method, or even better, return an enum or sealed class and let the ViewModel map this enum to the actual string resource, separating the concerns: your business logic class doesn't need to know about how to present the result to the UI, and ViewModel doesn't need to know about the business logic, just how to map the result to the UI representation, kinda:

    sealed class CompareResult {
        object NumberOneWins : CompareResult()
        object NumberTwoWins : CompareResult()
        object Tie : CompareResult()
    }
    
    class TestClass {
        fun compareNumbers(numberOne: Int, numberTwo: Int): CompareResult {
            return when {
                numberOne > numberTwo -> CompareResult.NumberOneWins
                numberOne < numberTwo -> CompareResult.NumberTwoWins
                else -> CompareResult.Tie
            }
        }
    }
    
    // In your ViewModel
    fun declareWinner() {
        val result = testClass.compareNumbers(numberOne, numberTwo)
        val message = when (result) {
            is CompareResult.NumberOneWins -> context.getString(R.string.numberOne)
            is CompareResult.NumberTwoWins -> context.getString(R.string.numberTwo)
            is CompareResult.Tie -> context.getString(R.string.tie)
        }
        updateResult(message)
    }
    
    

    Resulting in painless unit testing without the Context, and ViewModel becomes easier to test as well - you can mock compareNumbers method to return a specific CompareResult, and then just verify that updateResult was called with a correct string.