Merge "A little work on unit testing."
diff --git a/pom.xml b/pom.xml
index 90061b1..47252cb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,6 +32,11 @@
       <artifactId>robolectric</artifactId>
       <version>2.2</version>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+       <artifactId>mockito-core</artifactId>
+       <version>1.9.5</version>
+    </dependency>
   </dependencies>
 
   <build>
@@ -57,6 +62,13 @@
             <target>${java.version}</target>
           </configuration>
         </plugin>
+
+	<plugin>
+          <groupId>org.codehaus.mojo</groupId>
+          <artifactId>emma-maven-plugin</artifactId>
+          <version>1.0-alpha-3</version>
+        </plugin>
+
       </plugins>
     </pluginManagement>
   </build>
diff --git a/src/main/java/com/android/volley/toolbox/AndroidAuthenticator.java b/src/main/java/com/android/volley/toolbox/AndroidAuthenticator.java
index 371fd83..bdf7091 100644
--- a/src/main/java/com/android/volley/toolbox/AndroidAuthenticator.java
+++ b/src/main/java/com/android/volley/toolbox/AndroidAuthenticator.java
@@ -30,7 +30,7 @@
  * tokens of a specified type for a specified account.
  */
 public class AndroidAuthenticator implements Authenticator {
-    private final Context mContext;
+    private final AccountManager mAccountManager;
     private final Account mAccount;
     private final String mAuthTokenType;
     private final boolean mNotifyAuthFailure;
@@ -54,7 +54,13 @@
      */
     public AndroidAuthenticator(Context context, Account account, String authTokenType,
             boolean notifyAuthFailure) {
-        mContext = context;
+        this(AccountManager.get(context), account, authTokenType, notifyAuthFailure);
+    }
+
+    // Visible for testing. Allows injection of a mock AccountManager.
+    AndroidAuthenticator(AccountManager accountManager, Account account,
+            String authTokenType, boolean notifyAuthFailure) {
+        mAccountManager = accountManager;
         mAccount = account;
         mAuthTokenType = authTokenType;
         mNotifyAuthFailure = notifyAuthFailure;
@@ -71,8 +77,7 @@
     @SuppressWarnings("deprecation")
     @Override
     public String getAuthToken() throws AuthFailureError {
-        final AccountManager accountManager = AccountManager.get(mContext);
-        AccountManagerFuture<Bundle> future = accountManager.getAuthToken(mAccount,
+        AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(mAccount,
                 mAuthTokenType, mNotifyAuthFailure, null, null);
         Bundle result;
         try {
@@ -97,6 +102,6 @@
 
     @Override
     public void invalidateAuthToken(String authToken) {
-        AccountManager.get(mContext).invalidateAuthToken(mAccount.type, authToken);
+        mAccountManager.invalidateAuthToken(mAccount.type, authToken);
     }
 }
diff --git a/src/test/java/com/android/volley/toolbox/AndroidAuthenticatorTest.java b/src/test/java/com/android/volley/toolbox/AndroidAuthenticatorTest.java
new file mode 100644
index 0000000..e878658
--- /dev/null
+++ b/src/test/java/com/android/volley/toolbox/AndroidAuthenticatorTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.android.volley.toolbox;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import com.android.volley.AuthFailureError;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import static org.mockito.Mockito.*;
+
+@RunWith(RobolectricTestRunner.class)
+public class AndroidAuthenticatorTest {
+    private AccountManager mAccountManager;
+    private Account mAccount;
+    private AccountManagerFuture<Bundle> mFuture;
+    private AndroidAuthenticator mAuthenticator;
+
+    @Before
+    public void setUp() {
+        mAccountManager = mock(AccountManager.class);
+        mFuture = mock(AccountManagerFuture.class);
+        mAccount = new Account("coolperson", "cooltype");
+        mAuthenticator = new AndroidAuthenticator(mAccountManager, mAccount, "cooltype", false);
+    }
+
+    @Test(expected = AuthFailureError.class)
+    public void failedGetAuthToken() throws Exception {
+        when(mAccountManager.getAuthToken(mAccount, "cooltype", false, null, null)).thenReturn(mFuture);
+        when(mFuture.getResult()).thenThrow(new AuthenticatorException("sadness!"));
+        mAuthenticator.getAuthToken();
+    }
+
+    @Test(expected = AuthFailureError.class)
+    public void resultContainsIntent() throws Exception {
+        Intent intent = new Intent();
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
+        when(mAccountManager.getAuthToken(mAccount, "cooltype", false, null, null)).thenReturn(mFuture);
+        when(mFuture.getResult()).thenReturn(bundle);
+        when(mFuture.isDone()).thenReturn(true);
+        when(mFuture.isCancelled()).thenReturn(false);
+        mAuthenticator.getAuthToken();
+    }
+
+    @Test(expected = AuthFailureError.class)
+    public void missingAuthToken() throws Exception {
+        Bundle bundle = new Bundle();
+        when(mAccountManager.getAuthToken(mAccount, "cooltype", false, null, null)).thenReturn(mFuture);
+        when(mFuture.getResult()).thenReturn(bundle);
+        when(mFuture.isDone()).thenReturn(true);
+        when(mFuture.isCancelled()).thenReturn(false);
+        mAuthenticator.getAuthToken();
+    }
+
+    @Test
+    public void invalidateAuthToken() throws Exception {
+        mAuthenticator.invalidateAuthToken("monkey");
+        verify(mAccountManager).invalidateAuthToken("cooltype", "monkey");
+    }
+
+    @Test
+    public void goodToken() throws Exception {
+        Bundle bundle = new Bundle();
+        bundle.putString(AccountManager.KEY_AUTHTOKEN, "monkey");
+        when(mAccountManager.getAuthToken(mAccount, "cooltype", false, null, null)).thenReturn(mFuture);
+        when(mFuture.getResult()).thenReturn(bundle);
+        when(mFuture.isDone()).thenReturn(true);
+        when(mFuture.isCancelled()).thenReturn(false);
+        Assert.assertEquals("monkey", mAuthenticator.getAuthToken());
+    }
+
+    @Test
+    public void publicMethods() throws Exception {
+        // Catch-all test to find API-breaking changes.
+        Context context = mock(Context.class);
+        new AndroidAuthenticator(context, mAccount, "cooltype");
+        new AndroidAuthenticator(context, mAccount, "cooltype", true);
+        Assert.assertSame(mAccount, mAuthenticator.getAccount());
+    }
+}
diff --git a/src/test/java/com/android/volley/toolbox/ImageLoaderTest.java b/src/test/java/com/android/volley/toolbox/ImageLoaderTest.java
new file mode 100644
index 0000000..47c55a6
--- /dev/null
+++ b/src/test/java/com/android/volley/toolbox/ImageLoaderTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.android.volley.toolbox;
+
+import android.graphics.Bitmap;
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import static org.mockito.Mockito.*;
+
+@RunWith(RobolectricTestRunner.class)
+public class ImageLoaderTest {
+    private RequestQueue mRequestQueue;
+    private ImageLoader.ImageCache mImageCache;
+    private ImageLoader mImageLoader;
+
+    @Before
+    public void setUp() {
+        mRequestQueue = mock(RequestQueue.class);
+        mImageCache = mock(ImageLoader.ImageCache.class);
+        mImageLoader = new ImageLoader(mRequestQueue, mImageCache);
+    }
+
+    @Test
+    public void isCachedChecksCache() throws Exception {
+        when(mImageCache.getBitmap(anyString())).thenReturn(null);
+        Assert.assertFalse(mImageLoader.isCached("http://foo", 0, 0));
+    }
+
+    @Test
+    public void getWithCacheHit() throws Exception {
+        Bitmap bitmap = Bitmap.createBitmap(1, 1, null);
+        ImageLoader.ImageListener listener = mock(ImageLoader.ImageListener.class);
+        when(mImageCache.getBitmap(anyString())).thenReturn(bitmap);
+        ImageLoader.ImageContainer ic = mImageLoader.get("http://foo", listener);
+        Assert.assertSame(bitmap, ic.getBitmap());
+        verify(listener).onResponse(ic, true);
+    }
+
+    @Test
+    public void getWithCacheMiss() throws Exception {
+        when(mImageCache.getBitmap(anyString())).thenReturn(null);
+        ImageLoader.ImageListener listener = mock(ImageLoader.ImageListener.class);
+        // Ask for the image to be loaded.
+        mImageLoader.get("http://foo", listener);
+        // Second pass to test deduping logic.
+        mImageLoader.get("http://foo", listener);
+        // Response callback should be called both times.
+        verify(listener, times(2)).onResponse(any(ImageLoader.ImageContainer.class), eq(true));
+        // But request should be enqueued only once.
+        verify(mRequestQueue, times(1)).add(any(Request.class));
+    }
+
+    @Test
+    public void publicMethods() throws Exception {
+        // Catch API breaking changes.
+        ImageLoader.getImageListener(null, -1, -1);
+        mImageLoader.setBatchedResponseDelay(1000);
+    }
+}
+