Mock Retrofit using Dagger and Mockito

Retrofit is one of the most popular REST client for Android, if you never use it, it is high time to start. There are a lot of articles and tutorial talking about Retrofit. I just would like to show how to mock a REST server during develop of app and i…Retrofit is one of the most popular REST client for Android, if you never use it, it is high time to start. There are a lot of articles and tutorial talking about Retrofit. I just would like to show how to mock a REST server during develop of app and i…

Retrofit is one of the most popular REST client for Android, if you never use it, it is high time to start. There are a lot of articles and tutorial talking about Retrofit. I just would like to show how to mock a REST server during develop of app and in unit test when you are using Dagger as DI.

The example app will query the Echo REST serwer with:
http://echo.jsontest.com/message/sample_message/quantity/11the server responds with

{
    "message": "sample_message",
    "quantity": "11"
}

ApplicationI suggest to no using TDD this time and start from the app. Let’s look at the most significant part of implementation.

  • Interface of the service accordingly with Retrofit convention:
    public interface EchoService {
        @Headers("Content-Type: form-urlencoded; charset=utf-8")
        @GET("/message/{message}/quantity/{quantity}")
        EchoResponse getMessageAndQuantity(
                @Path("message") String message, 
                @Path("quantity") int quantity
        );
    }
  • Dagger provider that provides Retrofit service adapter
    @Module(
            injects = MainActivity.class,
            library = true,
            complete = false
    )
    public class RestServicesModule {
    
        @Provides
        @Named("realService")
        EchoService provideLogoutService() {
            return new RestAdapter.Builder()
                    .setServer("http://echo.jsontest.com")
                    .build()
                    .create(EchoService.class);
        }
    }

    As you can see I have also add @Named annotation. Of course it is not obligatory but I use it to inject real and mocked adapter, both in the same class. Check out the whole example on my Github to find out what I mean.

  • A piece of code that makes the query
@Inject
@Named("realService")
EchoService realService;

//some code

@Override
protected EchoResponse doInBackground(Void... params) {
    return realService.getMessageAndQuantity("example", "32");
}

In that way we make a call to server synchronously. It is not the most sophisticated example I can imagine, but the simplest showing how it works.

During develop of app I often would like to mock the server to get some kind of response (for example to check apps behaviour in some corner case) or just develop though server is down. Moreover it would be nice to be able to turn off/on mock in very simple and fast way.Thus I have write second Dagger module that provides mocked service adapter:

@Module(
        injects = MainActivity.class,
        library = true,
        complete = false
)
public class RestServicesMockModule {

    @Provides
    @Named("mockService")
    EchoService provideLogoutService(Client client) {
        return new RestAdapter.Builder()
                .setServer("http://echo.jsontest.com")
                .setClient(client)
                .build()
                .create(EchoService.class);
    }

    @Provides
    @Singleton
    Client provideMockClient() {
        return new RetrofitClientMock();
    }
}

//You certain noticed additional piece of code that set the Retrofit Client - in a nutshell the Client handle communication over the Internet. Thus we pass custom implementation of client. To keep clarify, implementation of the custom CLient is as simple as possible - always return the same response:
public class RetrofitClientMock implements Client {

    private static final int HTTP_OK_STATUS = 200;

    private static final String LOGIN_VALID_RESP = "{\n"
            + " \"message\": \"mock message\",\n"
            + " \"quantity\": \"11\"\n"
            + "}";

    @Override
    public Response execute(Request request) throws IOException {
        return createResponseWithCodeAndJson(HTTP_OK_STATUS, LOGIN_VALID_RESP);
    }

    private Response createResponseWithCodeAndJson(int responseCode, String json) {
        return new Response(responseCode, "nothing", Collections.EMPTY_LIST,
                new TypedByteArray("application/json", json.getBytes()));
    }
}
//You can activate the mock by adding the mock module to Dagger injector initialization.

Unit TestsDuring develop of app, you can send requests the server all time(or most of time) so it is possible to live without mocked serwer, it sucks but is possible. Unfortunately you are not able to write good tests without the mock. Below there are two unit tests. Actually they do not test anything but in simple way shows how to mock Retrofit service using Mockito and Dagger.

@RunWith(RobolectricTestRunner.class)
public class EchoServiceTest {

    @Inject
    protected EchoService loginService;

    @Inject
    protected Client client;

    @Before
    public void setUp() throws Exception {
        Injector.add(new AndroidModule(),
                new RestServicesModule(),
                new RestServicesMockModule(),
                new TestModule());
        Injector.inject(this);
    }

    @Test
    public void shouldReturnOfferInAsyncMode() throws IOException {
//given
        int expectedQuantity = 765;
        String responseContent = "{" +
                " \"message\": \"mock message\"," +
                " \"quantity\": \"" + expectedQuantity + "\"" +
                "}";
        mockResponseWithCodeAndContent(200, responseContent);

//when
        EchoResponse echoResponse = loginService.getMessageAndQuantity("test", "test");

//then
        assertThat(echoResponse.getQuantity()).isEqualTo(expectedQuantity);
    }

    @Test
    public void shouldReturnOfferInAsyncModea() throws IOException {
//given
        int expectedQuantity = 2;
        String responseContent = "{" +
                " \"message\": \"mock message\"," +
                " \"quantity\": \"" + expectedQuantity + "\"" +
                "}";
        mockResponseWithCodeAndContent(200, responseContent);

//when
        EchoResponse echoResponse = loginService.getMessageAndQuantity("test", "test");

//then
        assertThat(echoResponse.getQuantity()).isEqualTo(expectedQuantity);
    }


    protected void mockResponseWithCodeAndContent(int httpCode, String content) throws IOException {
        Response response = createResponseWithCodeAndJson(httpCode, content);
        when(client.execute(Matchers.anyObject())).thenReturn(response);
    }

    private Response createResponseWithCodeAndJson(int responseCode, String json) {
        return new Response(responseCode, "nothing", Collections.EMPTY_LIST, new TypedByteArray("application/json", json.getBytes()));
    }
}

And Dagger module for the tests:

@Module(
        injects = OfferDetailAdapterTest.class,
        overrides = true,
        library = true,
        complete = false

)
public class TestModule {

    @Provides
    EchoService provideLogoutService(Client client) {
        return new RestAdapter.Builder().setServer("http://echo.jsontest.com").setClient(client).build().create(EchoService.class);
    }

    @Provides
    @Singleton
    Client provideMockClient() {
        return mock(Client.class);
    }
}

 

Please notice very important detail. The mock Client provider method is annotated with @Singleton, it is obligatory to successfully mock the server in Test. If you miss @Singleton, then in runtime, there will be two instances of Client class. One in Test and another in instance of Activity class. Thus you operations on the client in Test class will have not any influence for behaviour in tested class.

The source code of the example you can find on my Github

You May Also Like

Atom Feeds with Spring MVC

How to add feeds (Atom) to your web application with just two classes?
How about Spring MVC?

Here are my assumptions:
  • you are using Spring framework
  • you have some entity, say “News”, that you want to publish in your feeds
  • your "News" entity has creationDate, title, and shortDescription
  • you have some repository/dao, say "NewsRepository", that will return the news from your database
  • you want to write as little as possible
  • you don't want to format Atom (xml) by hand
You actually do NOT need to use Spring MVC in your application already. If you do, skip to step 3.


Step 1: add Spring MVC dependency to your application
With maven that will be:
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>3.1.0.RELEASE</version>
</dependency>

Step 2: add Spring MVC DispatcherServlet
With web.xml that would be:
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/feed</url-pattern>
</servlet-mapping>
Notice, I set the url-pattern to “/feed” which means I don't want Spring MVC to handle any other urls in my app (I'm using a different web framework for the rest of the app). I also give it a brand new contextConfigLocation, where only the mvc configuration is kept.

Remember that, when you add a DispatcherServlet to an app that already has Spring (from ContextLoaderListener for example), your context is inherited from the global one, so you should not create beans that exist there again, or include xml that defines them. Watch out for Spring context getting up twice, and refer to spring or servlet documentation to understand what's happaning.

Step 3. add ROME – a library to handle Atom format
With maven that is:
<dependency>
    <groupId>net.java.dev.rome</groupId>
    <artifactId>rome</artifactId>
    <version>1.0.0</version>
</dependency>

Step 4. write your very simple controller
@Controller
public class FeedController {
    static final String LAST_UPDATE_VIEW_KEY = "lastUpdate";
    static final String NEWS_VIEW_KEY = "news";
    private NewsRepository newsRepository;
    private String viewName;

    protected FeedController() {} //required by cglib

    public FeedController(NewsRepository newsRepository, String viewName) {
        notNull(newsRepository); hasText(viewName);
        this.newsRepository = newsRepository;
        this.viewName = viewName;
    }

    @RequestMapping(value = "/feed", method = RequestMethod.GET)        
    @Transactional
    public ModelAndView feed() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName(viewName);
        List<News> news = newsRepository.fetchPublished();
        modelAndView.addObject(NEWS_VIEW_KEY, news);
        modelAndView.addObject(LAST_UPDATE_VIEW_KEY, getCreationDateOfTheLast(news));
        return modelAndView;
    }

    private Date getCreationDateOfTheLast(List<News> news) {
        if(news.size() > 0) {
            return news.get(0).getCreationDate();
        }
        return new Date(0);
    }
}
And here's a test for it, in case you want to copy&paste (who doesn't?):
@RunWith(MockitoJUnitRunner.class)
public class FeedControllerShould {
    @Mock private NewsRepository newsRepository;
    private Date FORMER_ENTRY_CREATION_DATE = new Date(1);
    private Date LATTER_ENTRY_CREATION_DATE = new Date(2);
    private ArrayList<News> newsList;
    private FeedController feedController;

    @Before
    public void prepareNewsList() {
        News news1 = new News().title("title1").creationDate(FORMER_ENTRY_CREATION_DATE);
        News news2 = new News().title("title2").creationDate(LATTER_ENTRY_CREATION_DATE);
        newsList = newArrayList(news2, news1);
    }

    @Before
    public void prepareFeedController() {
        feedController = new FeedController(newsRepository, "viewName");
    }

    @Test
    public void returnViewWithNews() {
        //given
        given(newsRepository.fetchPublished()).willReturn(newsList);
        
        //when
        ModelAndView modelAndView = feedController.feed();
        
        //then
        assertThat(modelAndView.getModel())
                .includes(entry(FeedController.NEWS_VIEW_KEY, newsList));
    }

    @Test
    public void returnViewWithLastUpdateTime() {
        //given
        given(newsRepository.fetchPublished()).willReturn(newsList);

        //when
        ModelAndView modelAndView = feedController.feed();

        //then
        assertThat(modelAndView.getModel())
                .includes(entry(FeedController.LAST_UPDATE_VIEW_KEY, LATTER_ENTRY_CREATION_DATE));
    }

    @Test
    public void returnTheBeginningOfTimeAsLastUpdateInViewWhenListIsEmpty() {
        //given
        given(newsRepository.fetchPublished()).willReturn(new ArrayList<News>());

        //when
        ModelAndView modelAndView = feedController.feed();

        //then
        assertThat(modelAndView.getModel())
                .includes(entry(FeedController.LAST_UPDATE_VIEW_KEY, new Date(0)));
    }
}
Notice: here, I'm using fest-assert and mockito. The dependencies are:
<dependency>
 <groupId>org.easytesting</groupId>
 <artifactId>fest-assert</artifactId>
 <version>1.4</version>
 <scope>test</scope>
</dependency>
<dependency>
 <groupId>org.mockito</groupId>
 <artifactId>mockito-all</artifactId>
 <version>1.8.5</version>
 <scope>test</scope>
</dependency>

Step 5. write your very simple view
Here's where all the magic formatting happens. Be sure to take a look at all the methods of Entry class, as there is quite a lot you may want to use/fill.
import org.springframework.web.servlet.view.feed.AbstractAtomFeedView;
[...]

public class AtomFeedView extends AbstractAtomFeedView {
    private String feedId = "tag:yourFantastiSiteName";
    private String title = "yourFantastiSiteName: news";
    private String newsAbsoluteUrl = "http://yourfanstasticsiteUrl.com/news/"; 

    @Override
    protected void buildFeedMetadata(Map<String, Object> model, Feed feed, HttpServletRequest request) {
        feed.setId(feedId);
        feed.setTitle(title);
        setUpdatedIfNeeded(model, feed);
    }

    private void setUpdatedIfNeeded(Map<String, Object> model, Feed feed) {
        @SuppressWarnings("unchecked")
        Date lastUpdate = (Date)model.get(FeedController.LAST_UPDATE_VIEW_KEY);
        if (feed.getUpdated() == null || lastUpdate != null || lastUpdate.compareTo(feed.getUpdated()) > 0) {
            feed.setUpdated(lastUpdate);
        }
    }

    @Override
    protected List<Entry> buildFeedEntries(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        @SuppressWarnings("unchecked")
        List<News> newsList = (List<News>)model.get(FeedController.NEWS_VIEW_KEY);
        List<Entry> entries = new ArrayList<Entry>();
        for (News news : newsList) {
            addEntry(entries, news);
        }
        return entries;
    }

    private void addEntry(List<Entry> entries, News news) {
        Entry entry = new Entry();
        entry.setId(feedId + ", " + news.getId());
        entry.setTitle(news.getTitle());
        entry.setUpdated(news.getCreationDate());
        entry = setSummary(news, entry);
        entry = setLink(news, entry);
        entries.add(entry);
    }

    private Entry setSummary(News news, Entry entry) {
        Content summary = new Content();
        summary.setValue(news.getShortDescription());
        entry.setSummary(summary);
        return entry;
    }

    private Entry setLink(News news, Entry entry) {
        Link link = new Link();
        link.setType("text/html");
        link.setHref(newsAbsoluteUrl + news.getId()); //because I have a different controller to show news at http://yourfanstasticsiteUrl.com/news/ID
        entry.setAlternateLinks(newArrayList(link));
        return entry;
    }

}

Step 6. add your classes to your Spring context
I'm using xml approach. because I'm old and I love xml. No, seriously, I use xml because I may want to declare FeedController a few times with different views (RSS 1.0, RSS 2.0, etc.).

So this is the forementioned spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="mediaTypes">
            <map>
                <entry key="atom" value="application/atom+xml"/>
                <entry key="html" value="text/html"/>
            </map>
        </property>
        <property name="viewResolvers">
            <list>
                <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
            </list>
        </property>
    </bean>

    <bean class="eu.margiel.pages.confitura.feed.FeedController">
        <constructor-arg index="0" ref="newsRepository"/>
        <constructor-arg index="1" value="atomFeedView"/>
    </bean>

    <bean id="atomFeedView" class="eu.margiel.pages.confitura.feed.AtomFeedView"/>
</beans>

And you are done.

I've been asked a few times before to put all the working code in some public repo, so this time it's the other way around. I've describe things that I had already published, and you can grab the commit from the bitbucket.

Hope that helps.