1

I using Spring Android Rest Template to perform HTTP request. I was testing like so :

package com.mnubo.platform.android.sdk.internal.user.services.impl;

import com.mnubo.platform.android.sdk.models.users.User;

import org.junit.Before;
import org.junit.Test;
import org.springframework.test.web.client.MockRestServiceServer;

import static org.springframework.http.HttpMethod.GET;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;

@RunWith(RobolectricTestRunner.class)
@Config(emulateSdk = 18)
public class DummyTest {

    private MyApiImpl myApi;
    private MockRestServiceServer myapiMockServer;

    @Before
    public void setUp() throws Exception {
    myApi = new MyApiImpl("ACCESS_TOKEN");//break here

    myapiMockServer = MockRestServiceServer.createServer(myApi.getRestTemplate());
    }

    @Test
    public void testRestClient() throws Exception {


    final User expectedUser = new User();
    expectedUser.setUsername("test");
    expectedUser.setLastname("lastname");

    myapiMockServer.expect(requestTo("http://my-service.com/user/test"))
            .andExpect(method(GET))
            .andRespond(withSuccess(this.convertToJsonString(expectedUser), APPLICATION_JSON_UTF8));

    User user = myApi.userOperations().getUser("test");
    userMockServer.verify();
    }
}

All of this was working correctly using the RoboelectricTestRunner. But, yesterday Android Studio updated and asked me to update the build tool version to 1.1.0. (com.android.tools.build:gradle:1.1.0).

This version now include supports for Unit testing. See https://sites.google.com/a/android.com/tools/tech-docs/unit-testing-support

The problem : I can't create MyApiImpl anymore because it creates a RestTemplate. This RestTemplate use org.springframework.http.client.HttpComponentsClientHttpRequestFactory and in this class constructor, methods from the org.apache.http package are used.

These methods raised an exception : Eg: Method getSocketFactory in org.apache.http.conn.scheme.PlainSocketFactory

Well, I mocked successfully the getSocketFactory using PowerMock but then I had to mock the register method of SchemeRegistry that is only locally accessible from the constructor (source).

So I gave up trying to mock all of the shenanigans happening inside the RestTemplate and decided to directly mock the RestTemplate.

I need help to mock it correctly so that the MockRestServiceServer can still be used in my test, because right now, the myapiMockServer.verify() assertion fails.

Update I'm still unable to use the MockRestServiceServer to test my Api, but I managed to test each individual OperationImpl using a mock of the RestTemplate like so :

public class DummyOperationTest {

    private DummyOperation dummyOperation;

    private RestTemplate mockedRestTemplate = mock(RestTemplate.class);


    @Before
    public void setUp() throws Exception {
    super.setUp();
    dummyOperation = new DummyOperationImpl(PLATFORM_BASE_URL, mockedRestTemplate);
    }

    @Test
    public void test() throws Exception {

    String calledUrl = PLATFORM_BASE_URL + "/objects?update_if_exists=true";
    when(mockedRestTemplate.postForObject(calledUrl, expectedSmartObject, SmartObject.class)).thenReturn(expectedSmartObject);

    smartObjectService.create(expectedSmartObject, true);
    verify(mockedRestTemplate, atMost(1)).postForObject(calledUrl, expectedSmartObject, SmartObject.class);


    }
}

Unfortunately, this still doesn't test the whole request execution. I can't validate that oAuth authentication is correctly added to the headers, or if the conversion of the server response is correct.

David
  • 1,101
  • 5
  • 19
  • 38

1 Answers1

1

Well, it was a matter of mocking. I didn't understood everything correctly but I finally worked it out. Here is an a example that shows how to proceed :

package com.mnubo.platform.android.sdk.internal;

import android.util.Log;

import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpParams;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.http.MediaType;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.web.client.MockRestServiceServer;

import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;
import static org.powermock.api.mockito.PowerMockito.whenNew;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;

@RunWith(PowerMockRunner.class)
@PrepareForTest({
        Log.class,
        PlainSocketFactory.class,
        SSLSocketFactory.class,
        HttpComponentsClientHttpRequestFactory.class,
        ConnManagerParams.class,
        AbstractHttpClient.class
})
public class DummyTest {

    private final String USER_ACCESS_TOKEN = "user_token";

    private final PlainSocketFactory mockedPlainSocketFactory = mock(PlainSocketFactory.class);
    private final SSLSocketFactory mockedSSLSocketFactory = mock(SSLSocketFactory.class);
    private final SchemeRegistry mockedSchemeRegistry = mock(SchemeRegistry.class);
    private final DefaultHttpClient mockedHttpClient = mock(DefaultHttpClient.class);
    private final HttpParams mockedHttpParams = mock(HttpParams.class);


    private DummyApiImpl dummyApi = new DummyApiImpl(USER_ACCESS_TOKEN);
    protected MockRestServiceServer mockServer;

    @Before
    public void setUp() throws Exception {
        mockStatic(Log.class);
        mockStatic(PlainSocketFactory.class);
        mockStatic(SchemeRegistry.class);
        mockStatic(SSLSocketFactory.class);
        mockStatic(ConnManagerParams.class);

        whenNew(SchemeRegistry.class).withAnyArguments().thenReturn(mockedSchemeRegistry);
        whenNew(DefaultHttpClient.class).withAnyArguments().thenReturn(mockedHttpClient);
        when(PlainSocketFactory.getSocketFactory()).thenReturn(mockedPlainSocketFactory);
        when(SSLSocketFactory.getSocketFactory()).thenReturn(mockedSSLSocketFactory);
        when(mockedHttpClient.getParams()).thenReturn(mockedHttpParams);

        mockServer = MockRestServiceServer.createServer(dummyApi.getRestTemplate());

    }

    @Test
    public void doOperationTest() throws Exception {

        final User testUser = new User();

        mockUserServiceServer.expect(requestTo(expectedUrl("/users/test")))
                .andExpect(method(POST))
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andRespond(withSuccess());

        dummyApi.userOperations().createUser("test", testUser);
        mockUserServiceServer.verify();
    }
}

The problem occurred when the constructor of the Api was called. This classe extends AbstractOAuth2ApiBinding which constructor create a RestTemplate. This is this object that requires multiple level of mocking.

Once you have mocked the required element to create the RestTemplate, the rest is very easy, thanks to the mock server!

David
  • 1,101
  • 5
  • 19
  • 38