Back to Repositories

Testing GIF Drawable Transformations in Glide Library

This test suite validates the GifDrawableTransformation class in Glide, focusing on bitmap transformations for GIF images and frame handling. It ensures proper transformation of GIF frames and validates equality comparisons for caching purposes.

Test Coverage Overview

The test suite covers critical aspects of GIF drawable transformations:
  • Frame transformation application to GIF drawables
  • Transformation equality and caching behavior
  • Bitmap pool integration
  • Resource handling and memory management

Implementation Analysis

The testing approach utilizes Robolectric for Android environment simulation and Mockito for mocking dependencies. It implements systematic validation of transformation chains and bitmap manipulations, with particular attention to frame-level transformations.

The tests employ mock objects to isolate the transformation logic and verify correct interaction between components.

Technical Details

Key technical components include:
  • JUnit 4 test framework
  • Robolectric test runner with SDK configuration
  • Mockito for mocking and verification
  • Custom KeyTester utility for equality testing
  • BitmapPool for resource management

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Proper test setup and teardown with @Before and @After annotations
  • Systematic resource cleanup
  • Comprehensive equality testing
  • Isolation of dependencies through mocking
  • Clear test method naming and organization

bumptech/glide

library/test/src/test/java/com/bumptech/glide/load/resource/gif/GifDrawableTransformationTest.java

            
package com.bumptech.glide.load.resource.gif;

import static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;
import static com.bumptech.glide.tests.Util.mockResource;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.graphics.Bitmap;
import androidx.test.core.app.ApplicationProvider;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.UnitTransformation;
import com.bumptech.glide.tests.KeyTester;
import com.bumptech.glide.tests.Util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
@Config(sdk = ROBOLECTRIC_SDK)
public class GifDrawableTransformationTest {
  @Rule public final KeyTester keyTester = new KeyTester();
  @Mock private Transformation<Bitmap> wrapped;
  @Mock private BitmapPool bitmapPool;

  private GifDrawableTransformation transformation;
  private Context context;

  @Before
  public void setUp() {
    MockitoAnnotations.initMocks(this);
    context = ApplicationProvider.getApplicationContext();

    Glide.init(context, new GlideBuilder().setBitmapPool(bitmapPool));
    transformation = new GifDrawableTransformation(wrapped);
  }

  @After
  public void tearDown() {
    Glide.tearDown();
  }

  @Test
  @SuppressWarnings("unchecked")
  public void testSetsTransformationAsFrameTransformation() {
    Resource<GifDrawable> resource = mockResource();
    GifDrawable gifDrawable = mock(GifDrawable.class);
    Transformation<Bitmap> unitTransformation = UnitTransformation.get();
    when(gifDrawable.getFrameTransformation()).thenReturn(unitTransformation);
    when(gifDrawable.getIntrinsicWidth()).thenReturn(500);
    when(gifDrawable.getIntrinsicHeight()).thenReturn(500);
    when(resource.get()).thenReturn(gifDrawable);

    Bitmap firstFrame = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    when(gifDrawable.getFirstFrame()).thenReturn(firstFrame);

    final int width = 123;
    final int height = 456;
    Bitmap expectedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    Resource<Bitmap> expectedResource = mockResource();
    when(expectedResource.get()).thenReturn(expectedBitmap);
    when(wrapped.transform(any(Context.class), Util.<Bitmap>anyResource(), anyInt(), anyInt()))
        .thenReturn(expectedResource);

    transformation.transform(context, resource, width, height);

    verify(gifDrawable).setFrameTransformation(isA(Transformation.class), eq(expectedBitmap));
  }

  @Test
  public void testEquals() throws NoSuchAlgorithmException {
    doAnswer(new Util.WriteDigest("first"))
        .when(wrapped)
        .updateDiskCacheKey(isA(MessageDigest.class));
    @SuppressWarnings("unchecked")
    Transformation<Bitmap> other = mock(Transformation.class);
    doAnswer(new Util.WriteDigest("other"))
        .when(other)
        .updateDiskCacheKey(isA(MessageDigest.class));
    keyTester
        .addEquivalenceGroup(
            transformation,
            new GifDrawableTransformation(wrapped),
            new GifDrawableTransformation(wrapped))
        .addEquivalenceGroup(wrapped)
        .addEquivalenceGroup(new GifDrawableTransformation(other))
        .addRegressionTest(
            new GifDrawableTransformation(wrapped),
            "a7937b64b8caa58f03721bb6bacf5c78cb235febe0e70b1b84cd99541461a08e")
        .test();
  }
}