Back to Repositories

Testing MultiModelLoader Factory Implementation in Glide

This test suite validates the MultiModelLoaderFactory class in Glide, focusing on model loader registration, ordering, and hierarchy handling. It ensures proper management of model loaders and their factories while verifying inheritance relationships and loader combinations.

Test Coverage Overview

The test suite provides comprehensive coverage of MultiModelLoaderFactory functionality including:
  • Model loader registration via append, prepend, and replace operations
  • Model and data class matching behavior
  • Inheritance and type hierarchy handling
  • Factory ordering and combination scenarios
  • Error cases and exception handling

Implementation Analysis

The testing approach employs JUnit with Robolectric for Android compatibility. It uses extensive mocking via Mockito to isolate components and verify interactions.

Key patterns include:
  • Setup of mock factories and loaders in @Before
  • Verification of loader ordering and registration
  • Type safety and inheritance testing
  • Exception validation for invalid scenarios

Technical Details

Testing tools and configuration:
  • JUnit 4 test framework
  • Robolectric for Android runtime simulation
  • Mockito for mocking and verification
  • Truth assertion library for improved test readability
  • Custom pools implementation for resource management

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Thorough setup and teardown management
  • Comprehensive edge case coverage
  • Clear test method naming and organization
  • Effective use of mocking to isolate components
  • Proper verification of complex object interactions

bumptech/glide

library/test/src/test/java/com/bumptech/glide/load/model/MultiModelLoaderFactoryTest.java

            
package com.bumptech.glide.load.model;

import static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import androidx.core.util.Pools.Pool;
import com.bumptech.glide.Registry.NoModelLoaderAvailableException;
import com.bumptech.glide.tests.Util;
import com.bumptech.glide.util.pool.FactoryPools;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

// containsExactly produces a spurious warning.
@SuppressWarnings("ResultOfMethodCallIgnored")
@RunWith(RobolectricTestRunner.class)
@Config(sdk = ROBOLECTRIC_SDK)
public class MultiModelLoaderFactoryTest {
  @Mock private ModelLoaderFactory<String, String> firstFactory;
  @Mock private ModelLoader<String, String> firstModelLoader;
  @Mock private MultiModelLoaderFactory.Factory multiModelLoaderFactory;
  @Mock private ModelLoaderFactory<String, String> secondFactory;
  @Mock private ModelLoader<String, String> secondModelLoader;

  private Pool<List<Throwable>> throwableListPool;
  private MultiModelLoaderFactory multiFactory;

  @Before
  public void setUp() {
    MockitoAnnotations.initMocks(this);
    throwableListPool = FactoryPools.threadSafeList();

    multiFactory = new MultiModelLoaderFactory(throwableListPool, multiModelLoaderFactory);
    when(firstFactory.build(eq(multiFactory))).thenReturn(firstModelLoader);
    when(secondFactory.build(eq(multiFactory))).thenReturn(secondModelLoader);
  }

  @Test
  public void testAppend_addsModelLoaderForModelClass() {
    multiFactory.append(String.class, String.class, firstFactory);

    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);
    assertThat(modelLoaders).containsExactly(firstModelLoader);
  }

  @Test
  public void testAppend_addsModelLoaderForModelAndDataClass() {
    multiFactory.append(String.class, String.class, firstFactory);

    ModelLoader<String, String> modelLoader = multiFactory.build(String.class, String.class);
    assertThat(modelLoader).isEqualTo(firstModelLoader);
  }

  @Test
  public void testPrepend_addsModelLoaderForModelClass() {
    multiFactory.prepend(String.class, String.class, firstFactory);

    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);
    assertThat(modelLoaders).containsExactly(firstModelLoader);
  }

  @Test
  public void testPrepend_addsModelLoaderForModelAndDataClass() {
    multiFactory.prepend(String.class, String.class, firstFactory);

    ModelLoader<String, String> modelLoader = multiFactory.build(String.class, String.class);
    assertThat(modelLoader).isEqualTo(firstModelLoader);
  }

  @Test
  public void testReplace_addsModelLoaderForModelClass() {
    multiFactory.replace(String.class, String.class, firstFactory);

    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);
    assertThat(modelLoaders).containsExactly(firstModelLoader);
  }

  @Test
  public void testReplace_addsModelLoaderForModelAndDataClasses() {
    multiFactory.replace(String.class, String.class, firstFactory);

    ModelLoader<String, String> modelLoader = multiFactory.build(String.class, String.class);
    assertThat(modelLoader).isEqualTo(firstModelLoader);
  }

  @Test
  public void testReplace_returnsPreviouslyRegisteredFactories_withModelAndDataClasses() {
    ModelLoaderFactory<String, String> firstOtherFactory = mockFactory();
    ModelLoaderFactory<String, String> secondOtherFactory = mockFactory();
    multiFactory.append(String.class, String.class, firstOtherFactory);
    multiFactory.append(String.class, String.class, secondOtherFactory);

    List<ModelLoaderFactory<? extends String, ? extends String>> removed =
        multiFactory.replace(String.class, String.class, firstFactory);
    assertThat(removed).containsExactly(firstOtherFactory, secondOtherFactory);
  }

  @Test
  public void testReplace_removesPreviouslyRegisteredFactories_withModelAndDataClasses() {
    appendFactoryFor(String.class, String.class);
    appendFactoryFor(String.class, String.class);

    multiFactory.replace(String.class, String.class, firstFactory);

    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);
    assertThat(modelLoaders).containsExactly(firstModelLoader);
  }

  @Test
  public void testRemove_returnsPreviouslyRegisteredFactories_withModelAndDataClasses() {
    ModelLoaderFactory<String, String> other = mockFactory();
    multiFactory.append(String.class, String.class, other);
    multiFactory.append(String.class, String.class, firstFactory);

    List<ModelLoaderFactory<? extends String, ? extends String>> removed =
        multiFactory.remove(String.class, String.class);
    assertThat(removed).containsExactly(firstFactory, other);
  }

  @Test
  public void testRemove_removesPreviouslyRegisteredFactories_withModelAndDataClasses() {
    appendFactoryFor(String.class, String.class);
    appendFactoryFor(String.class, String.class);

    multiFactory.remove(String.class, String.class);

    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);
    assertThat(modelLoaders).isEmpty();
  }

  @Test
  public void testBuild_withModelClass_returnsMultipleModelLoaders_ofGivenModelAndDataClasses() {
    ModelLoader<String, String> otherLoader = appendFactoryFor(String.class, String.class);
    multiFactory.append(String.class, String.class, firstFactory);

    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);
    assertThat(modelLoaders).containsExactly(otherLoader, firstModelLoader);
  }

  @Test
  public void
      testBuild_withModelClass_returnsMultipleModelLoaders_ofGivenModelClassWithDifferentDataClasses() {
    ModelLoader<String, Integer> otherLoader = appendFactoryFor(String.class, Integer.class);
    multiFactory.append(String.class, String.class, firstFactory);

    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);
    assertThat(modelLoaders).containsExactly(otherLoader, firstModelLoader);
  }

  @SuppressWarnings("TruthIncompatibleType")
  @Test
  public void testBuild_withModelClass_excludesModelLoadersForOtherModelClasses() {
    multiFactory.append(String.class, String.class, firstFactory);
    List<ModelLoader<Integer, ?>> modelLoaders = multiFactory.build(Integer.class);
    assertThat(modelLoaders)
        .doesNotContain(
            /* expected: ModelLoader<Integer, ?>, actual: ModelLoader<String, String> */ firstModelLoader);
  }

  @Test
  public void
      testBuild_withModelAndDataClasses_returnsMultipleModelLoaders_ofGivenModelAndDataClasses() {
    ModelLoader<String, String> otherLoader = appendFactoryFor(String.class, String.class);
    multiFactory.append(String.class, String.class, firstFactory);

    List<ModelLoader<String, String>> modelLoaders = buildModelLoaders(String.class, String.class);
    assertThat(modelLoaders).containsExactly(otherLoader, firstModelLoader);
  }

  @Test
  public void testBuild_withModelAndDataClasses_excludesModelLoadersForOtherDataClasses() {
    multiFactory.append(String.class, String.class, firstFactory);

    assertThrows(
        NoModelLoaderAvailableException.class,
        new ThrowingRunnable() {
          @Override
          public void run() throws Throwable {
            multiFactory.build(String.class, Integer.class);
          }
        });
  }

  @Test
  public void testBuild_withModelAndDataClasses_excludesModelLoadersForOtherModelClasses() {
    multiFactory.append(String.class, String.class, firstFactory);

    assertThrows(
        NoModelLoaderAvailableException.class,
        new ThrowingRunnable() {
          @Override
          public void run() throws Throwable {
            multiFactory.build(Integer.class, String.class);
          }
        });
  }

  @Test
  public void testBuild_withModelClass_doesNotMatchSubclassesOfModelClass() {
    ModelLoader<String, Object> subclass = appendFactoryFor(String.class, Object.class);
    List<ModelLoader<Object, ?>> modelLoaders = multiFactory.build(Object.class);
    assertThat(modelLoaders).doesNotContain(subclass);
  }

  @Test
  public void testBuild_withModelClass_matchesSuperclassesOfModelClass() {
    ModelLoader<Object, Object> superclass = appendFactoryFor(Object.class, Object.class);
    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);
    assertThat(modelLoaders).contains(superclass);
  }

  @Test
  public void testBuild_withModelAndDataClass_doesNotMatchSubclassesOfModelClass() {
    appendFactoryFor(String.class, Object.class);

    assertThrows(
        NoModelLoaderAvailableException.class,
        new ThrowingRunnable() {
          @Override
          public void run() throws Throwable {
            multiFactory.build(Object.class, Object.class);
          }
        });
  }

  @Test
  public void testBuild_withModelAndDataClass_doesNotMatchSubclassesOfDataClass() {
    appendFactoryFor(Object.class, String.class);
    assertThrows(
        NoModelLoaderAvailableException.class,
        new ThrowingRunnable() {
          @Override
          public void run() throws Throwable {
            multiFactory.build(Object.class, Object.class);
          }
        });
  }

  @Test
  public void testBuild_withModelAndDataClass_doesMatchSuperclassesOfModelClass() {
    ModelLoader<Object, Object> firstSuperClass = appendFactoryFor(Object.class, Object.class);
    ModelLoader<Object, Object> secondSuperClass = appendFactoryFor(Object.class, Object.class);
    List<ModelLoader<String, Object>> modelLoaders = buildModelLoaders(String.class, Object.class);
    assertThat(modelLoaders).containsExactly(firstSuperClass, secondSuperClass);
  }

  @Test
  public void testBuild_withModelAndDataClass_matchesSuperclassesOfDataClass() {
    ModelLoader<Object, Object> firstSuperClass = appendFactoryFor(Object.class, Object.class);
    ModelLoader<Object, Object> secondSuperClass = appendFactoryFor(Object.class, Object.class);
    List<ModelLoader<Object, String>> modelLoaders = buildModelLoaders(Object.class, String.class);
    assertThat(modelLoaders).containsExactly(firstSuperClass, secondSuperClass);
  }

  @Test
  public void testBuild_withModelAndDataClass_matchesSuperclassOfModelAndDataClass() {
    ModelLoader<Object, Object> firstSuperclass = appendFactoryFor(Object.class, Object.class);
    ModelLoader<Object, Object> secondSuperclass = appendFactoryFor(Object.class, Object.class);
    List<ModelLoader<String, String>> modelLoaders = buildModelLoaders(String.class, String.class);
    assertThat(modelLoaders).containsExactly(firstSuperclass, secondSuperclass);
  }

  @Test
  public void testBuild_respectsAppendOrder() {
    ModelLoader<String, String> first = appendFactoryFor(String.class, String.class);
    ModelLoader<String, String> second = appendFactoryFor(String.class, String.class);
    ModelLoader<String, String> third = appendFactoryFor(String.class, String.class);
    List<ModelLoader<String, String>> modelLoaders = buildModelLoaders(String.class, String.class);
    assertThat(modelLoaders).containsExactly(first, second, third).inOrder();
  }

  @Test
  public void testBuild_respectsPrependOrder() {
    ModelLoader<String, String> first = prependFactoryFor(String.class, String.class);
    ModelLoader<String, String> second = prependFactoryFor(String.class, String.class);
    ModelLoader<String, String> third = prependFactoryFor(String.class, String.class);
    List<ModelLoader<String, String>> modelLoaders = buildModelLoaders(String.class, String.class);
    assertThat(modelLoaders).containsExactly(third, second, first).inOrder();
  }

  private <X, Y> List<ModelLoader<X, Y>> buildModelLoaders(
      Class<X> modelClass, Class<Y> dataClass) {
    ArgumentCaptor<List<ModelLoader<X, Y>>> captor = Util.cast(ArgumentCaptor.forClass(List.class));
    multiFactory.build(modelClass, dataClass);
    verify(multiModelLoaderFactory).build(captor.capture(), eq(throwableListPool));

    List<ModelLoader<X, Y>> captured = captor.getValue();
    List<ModelLoader<X, Y>> result = new ArrayList<>(captured.size());
    result.addAll(captured);
    return result;
  }

  private <X, Y> ModelLoader<X, Y> appendFactoryFor(Class<X> modelClass, Class<Y> dataClass) {
    return registerFactoryFor(modelClass, dataClass, true /*append*/);
  }

  private <X, Y> ModelLoader<X, Y> prependFactoryFor(Class<X> modelClass, Class<Y> dataClass) {
    return registerFactoryFor(modelClass, dataClass, false /*append*/);
  }

  private <X, Y> ModelLoader<X, Y> registerFactoryFor(
      Class<X> modelClass, Class<Y> dataClass, boolean append) {
    ModelLoaderFactory<X, Y> factory = mockFactory();
    @SuppressWarnings("unchecked")
    ModelLoader<X, Y> loader = mock(ModelLoader.class);
    when(factory.build(eq(multiFactory))).thenReturn(loader);
    if (append) {
      multiFactory.append(modelClass, dataClass, factory);
    } else {
      multiFactory.prepend(modelClass, dataClass, factory);
    }
    return loader;
  }

  @SuppressWarnings("unchecked")
  private static <X, Y> ModelLoaderFactory<X, Y> mockFactory() {
    return mock(ModelLoaderFactory.class);
  }
}