[In_App_Purchase] Improve testability (#2016)

This PR creates a factory for BillingClient, with that was possible to remove a secondary constructor that was used just for test.

With the introduction of the factory there was the need to update some tests as it was testing states that would never be reached in the normal plugin flow (due the usage of the constructor for testing). Basically, the test cases depended on the state of BillingClient that would be null if no previous setup was executed.
diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md
index fbbb870..2f0e89e 100644
--- a/packages/in_app_purchase/CHANGELOG.md
+++ b/packages/in_app_purchase/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.2.1+3
+
+* Android : Improved testability.
+
 ## 0.2.1+2
 
 * Android: Require a non-null Activity to use the `launchBillingFlow` method.
diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java
new file mode 100644
index 0000000..078986c
--- /dev/null
+++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java
@@ -0,0 +1,19 @@
+package io.flutter.plugins.inapppurchase;
+
+import android.content.Context;
+import com.android.billingclient.api.BillingClient;
+import io.flutter.plugin.common.MethodChannel;
+
+interface BillingClientFactory {
+  BillingClient createBillingClient(Context context, MethodChannel channel);
+}
+
+final class BillingClientFactoryImpl implements BillingClientFactory {
+
+  @Override
+  public BillingClient createBillingClient(Context context, MethodChannel channel) {
+    return BillingClient.newBuilder(context)
+        .setListener(new PluginPurchaseListener(channel))
+        .build();
+  }
+}
diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
index e914d2a..99f6884 100644
--- a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
+++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
@@ -19,7 +19,6 @@
 import com.android.billingclient.api.ConsumeResponseListener;
 import com.android.billingclient.api.Purchase;
 import com.android.billingclient.api.PurchaseHistoryResponseListener;
-import com.android.billingclient.api.PurchasesUpdatedListener;
 import com.android.billingclient.api.SkuDetails;
 import com.android.billingclient.api.SkuDetailsParams;
 import com.android.billingclient.api.SkuDetailsResponseListener;
@@ -37,6 +36,7 @@
 public class InAppPurchasePlugin implements MethodCallHandler {
   private static final String TAG = "InAppPurchasePlugin";
   private @Nullable BillingClient billingClient;
+  private final BillingClientFactory factory;
   private final Registrar registrar;
   private final Context applicationContext;
   private final MethodChannel channel;
@@ -69,12 +69,17 @@
   public static void registerWith(Registrar registrar) {
     final MethodChannel channel =
         new MethodChannel(registrar.messenger(), "plugins.flutter.io/in_app_purchase");
-    channel.setMethodCallHandler(new InAppPurchasePlugin(registrar, channel));
+
+    final BillingClientFactory factory = new BillingClientFactoryImpl();
+    final InAppPurchasePlugin plugin = new InAppPurchasePlugin(factory, registrar, channel);
+    channel.setMethodCallHandler(plugin);
   }
 
-  public InAppPurchasePlugin(Registrar registrar, MethodChannel channel) {
+  public InAppPurchasePlugin(
+      BillingClientFactory factory, Registrar registrar, MethodChannel channel) {
     this.applicationContext = registrar.context();
     this.registrar = registrar;
+    this.factory = factory;
     this.channel = channel;
   }
 
@@ -112,18 +117,9 @@
     }
   }
 
-  @VisibleForTesting
-  /*package*/ InAppPurchasePlugin(
-      Registrar registrar, @Nullable BillingClient billingClient, MethodChannel channel) {
-    this.billingClient = billingClient;
-    this.channel = channel;
-    this.applicationContext = registrar.context();
-    this.registrar = registrar;
-  }
-
   private void startConnection(final int handle, final Result result) {
     if (billingClient == null) {
-      billingClient = buildBillingClient(applicationContext, channel);
+      billingClient = factory.createBillingClient(applicationContext, channel);
     }
 
     billingClient.startConnection(
@@ -282,27 +278,4 @@
     result.error("UNAVAILABLE", "BillingClient is unset. Try reconnecting.", null);
     return true;
   }
-
-  private static BillingClient buildBillingClient(Context context, MethodChannel channel) {
-    return BillingClient.newBuilder(context)
-        .setListener(new PluginPurchaseListener(channel))
-        .build();
-  }
-
-  @VisibleForTesting
-  /*package*/ static class PluginPurchaseListener implements PurchasesUpdatedListener {
-    private final MethodChannel channel;
-
-    PluginPurchaseListener(MethodChannel channel) {
-      this.channel = channel;
-    }
-
-    @Override
-    public void onPurchasesUpdated(int responseCode, @Nullable List<Purchase> purchases) {
-      final Map<String, Object> callbackArgs = new HashMap<>();
-      callbackArgs.put("responseCode", responseCode);
-      callbackArgs.put("purchasesList", fromPurchasesList(purchases));
-      channel.invokeMethod(MethodNames.ON_PURCHASES_UPDATED, callbackArgs);
-    }
-  }
 }
diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java
new file mode 100644
index 0000000..f1de27e
--- /dev/null
+++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java
@@ -0,0 +1,27 @@
+package io.flutter.plugins.inapppurchase;
+
+import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList;
+
+import androidx.annotation.Nullable;
+import com.android.billingclient.api.Purchase;
+import com.android.billingclient.api.PurchasesUpdatedListener;
+import io.flutter.plugin.common.MethodChannel;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class PluginPurchaseListener implements PurchasesUpdatedListener {
+  private final MethodChannel channel;
+
+  PluginPurchaseListener(MethodChannel channel) {
+    this.channel = channel;
+  }
+
+  @Override
+  public void onPurchasesUpdated(int responseCode, @Nullable List<Purchase> purchases) {
+    final Map<String, Object> callbackArgs = new HashMap<>();
+    callbackArgs.put("responseCode", responseCode);
+    callbackArgs.put("purchasesList", fromPurchasesList(purchases));
+    channel.invokeMethod(InAppPurchasePlugin.MethodNames.ON_PURCHASES_UPDATED, callbackArgs);
+  }
+}
diff --git a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java
index 41a6078..31118be 100644
--- a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java
+++ b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java
@@ -29,7 +29,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Activity;
-import android.content.Context;
 import androidx.annotation.Nullable;
 import com.android.billingclient.api.BillingClient;
 import com.android.billingclient.api.BillingClient.BillingResponse;
@@ -43,12 +42,10 @@
 import com.android.billingclient.api.SkuDetails;
 import com.android.billingclient.api.SkuDetailsParams;
 import com.android.billingclient.api.SkuDetailsResponseListener;
-import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.MethodChannel;
 import io.flutter.plugin.common.MethodChannel.Result;
 import io.flutter.plugin.common.PluginRegistry;
-import io.flutter.plugins.inapppurchase.InAppPurchasePlugin.PluginPurchaseListener;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -60,20 +57,19 @@
 import org.mockito.Spy;
 
 public class InAppPurchasePluginTest {
-  InAppPurchasePlugin plugin;
+  private InAppPurchasePlugin plugin;
   @Mock BillingClient mockBillingClient;
   @Mock MethodChannel mockMethodChannel;
   @Spy Result result;
   @Mock PluginRegistry.Registrar registrar;
   @Mock Activity activity;
-  @Mock BinaryMessenger messenger;
-  @Mock Context context;
 
   @Before
   public void setUp() {
     MockitoAnnotations.initMocks(this);
-    when(registrar.context()).thenReturn(context);
-    plugin = new InAppPurchasePlugin(registrar, mockBillingClient, mockMethodChannel);
+
+    BillingClientFactory factory = (context, channel) -> mockBillingClient;
+    plugin = new InAppPurchasePlugin(factory, registrar, mockMethodChannel);
   }
 
   @Test
@@ -85,6 +81,7 @@
 
   @Test
   public void isReady_true() {
+    mockStartConnection();
     MethodCall call = new MethodCall(IS_READY, null);
     when(mockBillingClient.isReady()).thenReturn(true);
     plugin.onMethodCall(call, result);
@@ -93,6 +90,7 @@
 
   @Test
   public void isReady_false() {
+    mockStartConnection();
     MethodCall call = new MethodCall(IS_READY, null);
     when(mockBillingClient.isReady()).thenReturn(false);
     plugin.onMethodCall(call, result);
@@ -113,14 +111,7 @@
 
   @Test
   public void startConnection() {
-    Map<String, Integer> arguments = new HashMap<>();
-    arguments.put("handle", 1);
-    MethodCall call = new MethodCall(START_CONNECTION, arguments);
-    ArgumentCaptor<BillingClientStateListener> captor =
-        ArgumentCaptor.forClass(BillingClientStateListener.class);
-    doNothing().when(mockBillingClient).startConnection(captor.capture());
-
-    plugin.onMethodCall(call, result);
+    ArgumentCaptor<BillingClientStateListener> captor = mockStartConnection();
     verify(result, never()).success(any());
     captor.getValue().onBillingSetupFinished(100);
 
@@ -452,6 +443,18 @@
     verify(result, times(1)).success(responseCode);
   }
 
+  private ArgumentCaptor<BillingClientStateListener> mockStartConnection() {
+    Map<String, Integer> arguments = new HashMap<>();
+    arguments.put("handle", 1);
+    MethodCall call = new MethodCall(START_CONNECTION, arguments);
+    ArgumentCaptor<BillingClientStateListener> captor =
+        ArgumentCaptor.forClass(BillingClientStateListener.class);
+    doNothing().when(mockBillingClient).startConnection(captor.capture());
+
+    plugin.onMethodCall(call, result);
+    return captor;
+  }
+
   private void establishConnectedBillingClient(
       @Nullable Map<String, Integer> arguments, @Nullable Result result) {
     if (arguments == null) {
diff --git a/packages/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/pubspec.yaml
index d3a8cf0..e8e9cee 100644
--- a/packages/in_app_purchase/pubspec.yaml
+++ b/packages/in_app_purchase/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play.
 author:  Flutter Team <flutter-dev@googlegroups.com>
 homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase
-version: 0.2.1+2
+version: 0.2.1+3
 
 
 dependencies: