Search code examples
androidtestingmockingretrofituitest

How to mock network layer responses for automated Android Studio UI Espresso test suite?


My goal is to run a suite of Espresso UI Tests, and have the Retrofit instance that has been created in the onCreate method of my MainActivity reconfigured to return mocked JSON responses from localhost, so I can test how the app's UI responds to response codes and different JSON data, etc.

So far, I've tried to drop in the MockWebServer package, and I was able to get that up and running on a localhost server. However, the Retrofit instance was still configured to the production url.

I've tried passing arguments at the launch of a test, including manually setting the intent for the MainActivity with some extras, in an attempt to set a flag that could toggle the string used for the Retrofit instance. However, it seems that the any arguments set in @Before of a test don't get picked up at the launch of the MainActivity.

I've looked at these answers, but there doesn't appear to be a solution:

Android Espresso UI Test with mocked Retrofit API

Mock server requests Android Espresso UI Testing

This is my latest experiment:

@get:Rule
    var activityRule = ActivityTestRule(MainActivity::class.java, true, false)

    @Before
    fun setup() {

    }

    @Test
    fun intent() {
        val intent = Intent()

        activityRule.launchActivity(intent.putExtra("UITesting", true))

        println(activityRule.activity.intent.extras)
    }

Here is the Retrofit instance:

var BASE_URL = "https://api.invoicehome.com"

class NetworkManager {

    private val service : networkService
    // initialization with BASE_URL, using Gson converter factory to decode ( i believe there are other options available as well)
    init {
        val retrofit = Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
        service = retrofit.create(networkService::class.java)
//    }
    }
 }

Beginning of MainActivity, where there's an instance of the NetworkManager:

class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {

    private var networkManager = NetworkManager()
}

I would like something that could simply wrap around the instance of Retrofit, and pop in a URL, but I'm 99% sure this isn't realistic. If any swift devs are looking at this, I'm looking for analogous functionality to Swifter.

So, can I somehow make the URL conditional for the Retrofit instance? Where would I pass in these flags? Is there a library that I'm missing that can do all of this for me?


Solution

  • You can use the help of MockWebServer library

    You will need to abstract your NetworkManager class and make two different implementations, one for mocking the requests and the other for making real requests and provide this as a dependency to your activity.

    So for example:

    NetworkManager Interface

    interface NetworkManager {
    
        fun doRequest()
    }
    

    Mock implementation

    class MockedNetworkManager : NetworkManager {
    
        override fun doRequest() {
            // Mock request
        }
    }
    

    Real implementation

    class RealNetworkManager : NetworkManager {
    
        override fun doRequest() {
            // Real request
        }
    }
    

    And your MainActivity should look something like this

    class MainActivity : AppCompatActivity() {
    
        private lateinit var networkManager: NetworkManager
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            val shouldMock = intent.getBooleanExtra("UITesting", false)
            if (shouldMock) {
                networkManager = MockedNetworkManager()
            } else {
                networkManager = RealNetworkManager()
            }
        }
    
    }