/* * Copyright 2015 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.samples.apps.iosched.videolibrary; import android.app.LoaderManager; import android.content.Context; import android.content.CursorLoader; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.test.suitebuilder.annotation.SmallTest; import com.google.samples.apps.iosched.provider.ScheduleContract; import com.google.samples.apps.iosched.util.LogUtils; import com.google.samples.apps.iosched.videolibrary.data.Video; import com.google.samples.apps.iosched.videolibrary.data.VideoTrack; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) @SmallTest public class VideoLibraryModelTest { /** * Cursor position in the {@link #FAKE_VIDEO_CURSOR_DATA} table. */ private int mCursorIndex = -1; /** * Fake data to be used for the mocked cursor. */ private static final Object[][] FAKE_VIDEO_CURSOR_DATA = { new String[]{"ID1", "ID2", "ID3"}, new Integer[]{2012, 2013, 2012}, new String[]{"Android", "Android", "APIs"}, new String[]{"Title 1", "Title 2", "Title 3"}, new String[]{"Desc 1", "Desc 2", "Desc 3"}, new String[]{"VID1", "VID2", "VID3"}, new String[]{"Speaker Name 1", "Speaker Name 2", "Speaker Name 3"}, new String[]{"http://url.com/1", "http://url.com/2", "http://url.com/3"}}; /** * Indexes of the "column" for each video attribute in the {@link #FAKE_VIDEO_CURSOR_DATA}. */ private static final int VIDEO_ID_COLUMN_INDEX = 0; private static final int VIDEO_YEAR_COLUMN_INDEX = 1; private static final int VIDEO_TOPIC_COLUMN_INDEX = 2; private static final int VIDEO_TITLE_COLUMN_INDEX = 3; private static final int VIDEO_DESC_COLUMN_INDEX = 4; private static final int VIDEO_VID_COLUMN_INDEX = 5; private static final int VIDEO_SPEAKER_COLUMN_INDEX = 6; private static final int VIDEO_THUMBNAIL_URL_COLUMN_INDEX = 7; @Mock private Context mMockContext; @Mock private Cursor mMockCursor; @Mock private Bundle mMockBundle; @Mock private CursorLoader mMockCursorLoader; @Mock private LoaderManager mMockLoaderManager; @Spy private VideoLibraryModel mSpyModel = new VideoLibraryModel(mMockContext, mMockLoaderManager, Uri.EMPTY, Uri.EMPTY, Uri.EMPTY); @Captor private ArgumentCaptor<Integer> mGetCursorValueCaptor; private VideoLibraryModel mVideoLibraryModel; @Before public void initMocks() { // Initialize Mocks and Spies. initWithStubbedCursor(); initWithStubbedCursorLoaderAndSpyModel(); // Disable logging LogUtils.LOGGING_ENABLED = false; // Create an instance of the model. mVideoLibraryModel = new VideoLibraryModel(mMockContext, mMockLoaderManager, Uri.EMPTY, Uri.EMPTY, Uri.EMPTY); } @Test public void readDataFromCursor_VideosQuery_VideosLoaded() { // Given a mock cursor with data ran with a valid query. boolean success = mVideoLibraryModel.readDataFromCursor( mMockCursor, VideoLibraryModel.VideoLibraryQueryEnum.VIDEOS); // Then the model is updated and the request succeeds. assertThat(success, is(true)); // Check that all Videos have been filled properly. assertThat(mVideoLibraryModel.getVideos().size(), is(2)); int indexInFakeCursor = 0; for (int i = 0; i < mVideoLibraryModel.getVideos().size(); i++) { VideoTrack videoTrack = mVideoLibraryModel.getVideos().get(i); for (int j = 0; j < videoTrack.getVideos().size(); j++) { assertThat(videoTrack, is(equalsVideoDataInCursor(FAKE_VIDEO_CURSOR_DATA, j, indexInFakeCursor))); indexInFakeCursor += 1; } } } @Test public void readDataFromCursor_FiltersQuery_FiltersLoaded() { // Given a mock cursor with data ran with a valid query boolean success = mVideoLibraryModel.readDataFromCursor( mMockCursor, VideoLibraryModel.VideoLibraryQueryEnum.FILTERS); // Then the model is updated and the request succeeds assertThat(success, is(true)); // Then the model is updated and the request succeeds assertThat(mVideoLibraryModel.getTopics(), hasItems((String[]) FAKE_VIDEO_CURSOR_DATA[VIDEO_TOPIC_COLUMN_INDEX])); assertThat(mVideoLibraryModel.getYears(), hasItems((Integer[]) FAKE_VIDEO_CURSOR_DATA[VIDEO_YEAR_COLUMN_INDEX])); } @Test public void readDataFromCursor_EmptyCursor_EmptyResult() { // Given an empty mock cursor. when(mMockCursor.moveToFirst()).thenReturn(false); // When ran with a valid query. boolean result = mVideoLibraryModel.readDataFromCursor(mMockCursor, VideoLibraryModel.VideoLibraryQueryEnum.VIDEOS); // Then the request model update succeeds. assertThat(result, is(true)); // And the list of videos is empty. assertNull(mVideoLibraryModel.getVideos()); } @Test public void createCursorLoader_VideosQuery_Success() { // Given a mock cursor loader set up for a video query // When ran with the video query CursorLoader createdCursorLoader = (CursorLoader) mSpyModel .createCursorLoader(VideoLibraryModel.VideoLibraryQueryEnum.VIDEOS, null); // Then the returned cursor loader is the same as the mock one assertThat(createdCursorLoader, sameInstance(mMockCursorLoader)); } @Test public void createCursorLoader_FilteredVideosQuery_Success() { // Given a mock cursor loader set up for a video query when(mMockBundle.containsKey(VideoLibraryModel.KEY_TOPIC)).thenReturn(true); when(mMockBundle.containsKey(VideoLibraryModel.KEY_YEAR)).thenReturn(true); when(mMockBundle.getString(VideoLibraryModel.KEY_TOPIC)).thenReturn("Android"); when(mMockBundle.getInt(VideoLibraryModel.KEY_YEAR)).thenReturn(2012); // When ran with the video query CursorLoader createdCursorLoader = (CursorLoader) mSpyModel .createCursorLoader(VideoLibraryModel.VideoLibraryQueryEnum.VIDEOS, mMockBundle); // Then the returned cursor loader is the same as the mock one assertThat(createdCursorLoader, sameInstance(mMockCursorLoader)); } @Test public void createCursorLoader_FiltersQuery_Success() { // Given a mock cursor loader set up for a video query // When ran with the video query CursorLoader createdCursorLoader = (CursorLoader) mSpyModel .createCursorLoader(VideoLibraryModel.VideoLibraryQueryEnum.FILTERS, null); // Then the returned cursor loader is the same as the mock one assertThat(createdCursorLoader, sameInstance(mMockCursorLoader)); } private void initWithStubbedCursorLoaderAndSpyModel() { doReturn(mMockCursorLoader).when(mSpyModel).getCursorLoaderInstance( any(Context.class), any(Uri.class), any(String[].class), any(String.class), any(String[].class), any(String.class)); } /** * Initializes the {@code mMockCursor} so that it returns data from the {@link * #FAKE_VIDEO_CURSOR_DATA} table. */ private void initWithStubbedCursor() { // Mock movements of the cursor position. Cursor position in the array is tracked using // mCursorIndex. when(mMockCursor.moveToFirst()).then( new Answer<Boolean>() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { mCursorIndex = 0; return FAKE_VIDEO_CURSOR_DATA[0].length > 0; } }); when(mMockCursor.moveToNext()).then( new Answer<Boolean>() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { if (mCursorIndex < FAKE_VIDEO_CURSOR_DATA[0].length - 1) { mCursorIndex++; return true; } else { return false; } } }); // Mock reading data from the cursor using the FAKE_VIDEO_CURSOR_DATA table values. Answer<?> getCursorValue = new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return FAKE_VIDEO_CURSOR_DATA[mGetCursorValueCaptor.getValue()][mCursorIndex]; } }; // Map the fake data table column index. when(mMockCursor.getColumnIndex(ScheduleContract.Videos.VIDEO_ID)) .thenReturn(VIDEO_ID_COLUMN_INDEX); when(mMockCursor.getColumnIndex(ScheduleContract.Videos.VIDEO_YEAR)) .thenReturn(VIDEO_YEAR_COLUMN_INDEX); when(mMockCursor.getColumnIndex(ScheduleContract.Videos.VIDEO_TOPIC)) .thenReturn(VIDEO_TOPIC_COLUMN_INDEX); when(mMockCursor.getColumnIndex(ScheduleContract.Videos.VIDEO_TITLE)) .thenReturn(VIDEO_TITLE_COLUMN_INDEX); when(mMockCursor.getColumnIndex(ScheduleContract.Videos.VIDEO_DESC)) .thenReturn(VIDEO_DESC_COLUMN_INDEX); when(mMockCursor.getColumnIndex(ScheduleContract.Videos.VIDEO_VID)) .thenReturn(VIDEO_VID_COLUMN_INDEX); when(mMockCursor.getColumnIndex(ScheduleContract.Videos.VIDEO_SPEAKERS)) .thenReturn(VIDEO_SPEAKER_COLUMN_INDEX); when(mMockCursor.getColumnIndex(ScheduleContract.Videos.VIDEO_THUMBNAIL_URL)) .thenReturn(VIDEO_THUMBNAIL_URL_COLUMN_INDEX); // Returning values from the fake data table. when(mMockCursor.getString(mGetCursorValueCaptor.capture())).then(getCursorValue); when(mMockCursor.getInt(mGetCursorValueCaptor.capture())).then(getCursorValue); } /** * Checks that the given {@code VideoLibraryModel.Video} is equal to the video data in the given * cursor table at the given {@code index}. */ private Matcher<VideoTrack> equalsVideoDataInCursor(final Object[][] cursorTable, final int indexInTrack, final int indexInCursorTable) { return new BaseMatcher<VideoTrack>() { @Override public boolean matches(final Object item) { final Video video = ((VideoTrack) item).getVideos().get(indexInTrack); return video.getId().equals(cursorTable[VIDEO_ID_COLUMN_INDEX][indexInCursorTable]) && video.getYear() == (Integer) cursorTable[VIDEO_YEAR_COLUMN_INDEX][indexInCursorTable] && video.getTopic() .equals(cursorTable[VIDEO_TOPIC_COLUMN_INDEX][indexInCursorTable]) && video.getTitle() .equals(cursorTable[VIDEO_TITLE_COLUMN_INDEX][indexInCursorTable]) && video.getDesc() .equals(cursorTable[VIDEO_DESC_COLUMN_INDEX][indexInCursorTable]) && video.getVid() .equals(cursorTable[VIDEO_VID_COLUMN_INDEX][indexInCursorTable]) && video.getSpeakers().equals( cursorTable[VIDEO_SPEAKER_COLUMN_INDEX][indexInCursorTable]) && video.getThumbnailUrl().equals( cursorTable[VIDEO_THUMBNAIL_URL_COLUMN_INDEX][indexInCursorTable]); } @Override public void describeTo(final Description description) { description.appendText("The Video does not match the data in table ") .appendValue(cursorTable).appendText(" at index in track ") .appendValue(indexInTrack) .appendValue(cursorTable).appendText(" at index in table ") .appendValue(indexInCursorTable); } }; } }