Back to Repositories

Testing Concurrent Future Operations in Glide Image Loading Framework

This test suite validates the integration between Glide’s Future-based image loading functionality and Android’s concurrent operations. It ensures proper handling of asynchronous image loading, error cases, and resource management in the Glide library.

Test Coverage Overview

The test suite covers critical aspects of Glide’s Future-based operations including:
  • Basic image loading functionality with ColorDrawable
  • Error handling for unsupported model types
  • Custom model loading with mock implementations
  • Resource decoder integration
  • Cancellation behavior of Future operations

Implementation Analysis

The testing approach utilizes Robolectric for Android environment simulation and JUnit for test execution. It implements mock executors to control threading behavior and employs Guava’s ListenableFuture for asynchronous operations testing.

Key patterns include dependency injection for executors, mock model loading, and custom resource decoder implementation for edge case testing.

Technical Details

Testing tools and configuration:
  • RobolectricTestRunner for Android runtime simulation
  • MockGlideExecutor for controlled threading
  • Guava’s Future utilities for async operations
  • Custom ResourceDecoder implementation
  • ApplicationProvider for context management

Best Practices Demonstrated

The test suite exemplifies high-quality integration testing through:
  • Proper test isolation with @Before setup
  • Comprehensive error case validation
  • Mock object utilization for controlled testing
  • Clear test method naming conventions
  • Efficient resource cleanup and cancellation handling

bumptech/glide

integration/concurrent/src/test/java/com/bumptech/glide/integration/concurrent/GlideFuturesTest.java

            
package com.bumptech.glide.integration.concurrent;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import androidx.test.core.app.ApplicationProvider;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.ResourceDecoder;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.engine.executor.GlideExecutor;
import com.bumptech.glide.load.engine.executor.MockGlideExecutor;
import com.bumptech.glide.testutil.MockModelLoader;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import org.junit.Before;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;

@RunWith(RobolectricTestRunner.class)
public final class GlideFuturesTest {

  private Context app;

  @Before
  public void setUp() {
    app = ApplicationProvider.getApplicationContext();

    GlideExecutor executor = MockGlideExecutor.newMainThreadExecutor();
    Glide.init(
        app,
        new GlideBuilder()
            .setAnimationExecutor(executor)
            .setSourceExecutor(executor)
            .setDiskCacheExecutor(executor));
  }

  @Test
  public void testBaseLoad() throws Exception {
    ColorDrawable expected = new ColorDrawable(Color.RED);
    ListenableFuture<Drawable> future = GlideFutures.submit(Glide.with(app).load(expected));
    assertThat(((ColorDrawable) Futures.getDone(future)).getColor()).isEqualTo(expected.getColor());
  }

  @Test
  public void testErrorLoad() {
    // Load some unsupported model.
    final ListenableFuture<Bitmap> future =
        GlideFutures.submit(Glide.with(app).asBitmap().load(app));
    // Make sure that it throws.
    assertThrows(
        ExecutionException.class,
        new ThrowingRunnable() {
          @Override
          public void run() throws Throwable {
            Futures.getDone(future);
          }
        });
  }

  @Test
  public void testToString() throws Exception {
    Foo model = new Foo();
    SettableFuture<Bar> bar = SettableFuture.create();

    Glide.get(app)
        .getRegistry()
        .prepend(
            Bar.class,
            Baz.class,
            new ResourceDecoder<Bar, Baz>() {

              @Override
              public boolean handles(Bar source, Options options) throws IOException {
                return true;
              }

              @Override
              public Resource<Baz> decode(Bar source, int width, int height, Options options)
                  throws IOException {
                throw new IOException();
              }
            });
    MockModelLoader.mockAsync(model, Bar.class, bar);
    ListenableFuture<Baz> future =
        GlideFutures.submit(
            Glide.with(app)
                .as(Baz.class)
                .load(model)
                .skipMemoryCache(true)
                .diskCacheStrategy(DiskCacheStrategy.NONE));
    assertThat(future.toString()).contains("Foo");
    future.cancel(true);
    assertThat(bar.isCancelled()).isTrue();
  }

  private static final class Foo {}

  private static final class Bar {}

  private static final class Baz {}
}