Back to Repositories

Validating RequestManager Lifecycle Integration in Glide

This test suite validates the RequestManager class in Glide, a crucial component handling image loading lifecycle and request management. It ensures proper request tracking, lifecycle integration, and connectivity monitoring functionality.

Test Coverage Overview

The test suite provides comprehensive coverage of RequestManager functionality including request lifecycle management, connectivity handling, and hierarchical request tracking. Key areas tested include:
  • Request pause/resume operations
  • Lifecycle event handling (start/stop/destroy)
  • Connectivity state management
  • Parent-child relationship handling
  • Background thread operations

Implementation Analysis

The testing approach utilizes JUnit with Robolectric for Android environment simulation. Mockito is extensively used for mocking dependencies and verifying interactions. The implementation demonstrates proper separation of concerns through:
  • Isolated component testing
  • Comprehensive mock setup
  • Lifecycle event verification
  • Thread safety validation

Technical Details

Testing infrastructure includes:
  • Robolectric test runner with SDK configuration
  • MockitoAnnotations for mock initialization
  • Custom test rules for Glide teardown
  • ConnectivityMonitor mocking for network state testing
  • CustomTarget implementation for request verification

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Proper test setup and teardown management
  • Comprehensive edge case coverage
  • Clear test method naming conventions
  • Effective use of mocking for isolation
  • Thorough verification of component interactions

bumptech/glide

library/test/src/test/java/com/bumptech/glide/RequestManagerTest.java

            
package com.bumptech.glide;

import static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;
import static com.bumptech.glide.tests.BackgroundUtil.testInBackground;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Application;
import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import com.bumptech.glide.manager.ConnectivityMonitor;
import com.bumptech.glide.manager.ConnectivityMonitor.ConnectivityListener;
import com.bumptech.glide.manager.ConnectivityMonitorFactory;
import com.bumptech.glide.manager.Lifecycle;
import com.bumptech.glide.manager.RequestManagerTreeNode;
import com.bumptech.glide.manager.RequestTracker;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.bumptech.glide.tests.BackgroundUtil;
import com.bumptech.glide.tests.TearDownGlide;
import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
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.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
@Config(sdk = ROBOLECTRIC_SDK)
public class RequestManagerTest {
  @Rule public TearDownGlide tearDownGlide = new TearDownGlide();

  @Mock private Lifecycle lifecycle = mock(Lifecycle.class);
  @Mock private RequestManagerTreeNode treeNode = mock(RequestManagerTreeNode.class);

  private RequestManager manager;
  private ConnectivityMonitor connectivityMonitor;
  private RequestTracker requestTracker;
  private ConnectivityListener connectivityListener;
  private Application context;
  private CustomTarget<Drawable> target;

  @Before
  public void setUp() {
    MockitoAnnotations.initMocks(this);
    context = ApplicationProvider.getApplicationContext();
    connectivityMonitor = mock(ConnectivityMonitor.class);
    ConnectivityMonitorFactory factory = mock(ConnectivityMonitorFactory.class);
    when(factory.build(isA(Context.class), isA(ConnectivityMonitor.ConnectivityListener.class)))
        .thenAnswer(
            new Answer<ConnectivityMonitor>() {
              @Override
              public ConnectivityMonitor answer(InvocationOnMock invocation) {
                connectivityListener = (ConnectivityListener) invocation.getArguments()[1];
                return connectivityMonitor;
              }
            });

    target =
        new CustomTarget<Drawable>() {
          @Override
          public void onResourceReady(
              @NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
            // Empty.
          }

          @Override
          public void onLoadCleared(@Nullable Drawable placeholder) {}
        };

    requestTracker = mock(RequestTracker.class);
    manager =
        new RequestManager(
            Glide.get(ApplicationProvider.getApplicationContext()),
            lifecycle,
            treeNode,
            requestTracker,
            factory,
            context);
  }

  @Test
  public void testPauseRequestsPausesRequests() {
    manager.pauseRequests();

    verify(requestTracker).pauseRequests();
  }

  @Test
  public void testResumeRequestsResumesRequests() {
    manager.resumeRequests();

    verify(requestTracker).resumeRequests();
  }

  @Test
  public void testPausesRequestsOnStop() {
    manager.onStart();
    manager.onStop();

    verify(requestTracker).pauseRequests();
  }

  @Test
  public void testResumesRequestsOnStart() {
    manager.onStart();

    verify(requestTracker).resumeRequests();
  }

  @Test
  public void testClearsRequestsOnDestroy() {
    manager.onDestroy();

    verify(requestTracker).clearRequests();
  }

  @Test
  public void testAddsConnectivityMonitorToLifecycleWhenConstructed() {
    verify(lifecycle).addListener(eq(connectivityMonitor));
  }

  @Test
  public void testAddsSelfToLifecycleWhenConstructed() {
    verify(lifecycle).addListener(eq(manager));
  }

  @Test
  public void testRestartsRequestOnConnected() {
    connectivityListener.onConnectivityChanged(true);

    verify(requestTracker).restartRequests();
  }

  @Test
  public void testDoesNotRestartRequestsOnDisconnected() {
    connectivityListener.onConnectivityChanged(false);

    verify(requestTracker, never()).restartRequests();
  }

  @Test
  public void resumeRequests_whenCalledOnBackgroundThread_doesNotThrow()
      throws InterruptedException {
    testInBackground(
        new BackgroundUtil.BackgroundTester() {
          @Override
          public void runTest() {
            manager.resumeRequests();
          }
        });
  }

  @Test
  public void pauseRequests_whenCalledOnBackgroundThread_doesNotThrow()
      throws InterruptedException {
    testInBackground(
        new BackgroundUtil.BackgroundTester() {
          @Override
          public void runTest() {
            manager.pauseRequests();
          }
        });
  }

  @Test
  public void testDelegatesIsPausedToRequestTracker() {
    when(requestTracker.isPaused()).thenReturn(true);
    assertTrue(manager.isPaused());
    when(requestTracker.isPaused()).thenReturn(false);
    assertFalse(manager.isPaused());
  }

  @Test
  public void clear_withRequestStartedInSiblingManager_doesNotThrow() {
    final RequestManager child1 =
        new RequestManager(
            Glide.get(context),
            lifecycle,
            new RequestManagerTreeNode() {
              @NonNull
              @Override
              public Set<RequestManager> getDescendants() {
                return Collections.emptySet();
              }
            },
            context);
    final RequestManager child2 =
        new RequestManager(
            Glide.get(context),
            lifecycle,
            new RequestManagerTreeNode() {
              @NonNull
              @Override
              public Set<RequestManager> getDescendants() {
                return Collections.emptySet();
              }
            },
            context);
    new RequestManager(
        Glide.get(context),
        lifecycle,
        new RequestManagerTreeNode() {
          @NonNull
          @Override
          public Set<RequestManager> getDescendants() {
            return new HashSet<>(java.util.Arrays.asList(child1, child2));
          }
        },
        context);

    File file = new File("fake");
    child1.load(file).into(target);
    child2.clear(target);
  }

  @Test
  public void clear_withRequestStartedInChildManager_doesNotThrow() {
    final RequestManager child =
        new RequestManager(
            Glide.get(context),
            lifecycle,
            new RequestManagerTreeNode() {
              @NonNull
              @Override
              public Set<RequestManager> getDescendants() {
                return Collections.emptySet();
              }
            },
            context);
    RequestManager parent =
        new RequestManager(
            Glide.get(context),
            lifecycle,
            new RequestManagerTreeNode() {
              @NonNull
              @Override
              public Set<RequestManager> getDescendants() {
                return Collections.singleton(child);
              }
            },
            context);

    File file = new File("fake");
    child.load(file).into(target);
    parent.clear(target);
  }

  @Test
  public void clear_withRequestStartedInParentManager_doesNotThrow() {
    final RequestManager child =
        new RequestManager(
            Glide.get(context),
            lifecycle,
            new RequestManagerTreeNode() {
              @NonNull
              @Override
              public Set<RequestManager> getDescendants() {
                return Collections.emptySet();
              }
            },
            context);
    RequestManager parent =
        new RequestManager(
            Glide.get(context),
            lifecycle,
            new RequestManagerTreeNode() {
              @NonNull
              @Override
              public Set<RequestManager> getDescendants() {
                return Collections.singleton(child);
              }
            },
            context);

    File file = new File("fake");

    parent.load(file).into(target);
    child.clear(target);
  }
}