Back to Repositories

Testing LottieCompositionFactory Loading and Caching Implementation in lottie-android

This test suite validates the LottieCompositionFactory functionality in the Lottie Android library, focusing on JSON parsing, caching mechanisms, and error handling for animation compositions. The tests ensure reliable loading and processing of Lottie animation files across different input formats.

Test Coverage Overview

The test suite provides comprehensive coverage of the LottieCompositionFactory’s core functionality.

  • JSON string parsing and validation
  • Cache hit/miss scenarios
  • Asset loading and resource handling
  • Error cases with invalid inputs
  • Stream handling and reader implementations

Implementation Analysis

The testing approach utilizes JUnit framework with Robolectric for Android environment simulation. Tests employ systematic validation of both synchronous and asynchronous composition loading methods.

Key patterns include:
  • Before-test cache initialization
  • Multiple assertion chains for comprehensive validation
  • Explicit null checking for error cases
  • Cache behavior verification

Technical Details

Testing infrastructure includes:

  • JUnit 4 testing framework
  • Robolectric for Android runtime simulation
  • Okio for efficient I/O operations
  • Moshi JsonReader for JSON parsing
  • Custom InputStream implementation for edge cases

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases with clear setup/teardown
  • Comprehensive error case handling
  • Cache verification patterns
  • Resource cleanup
  • Boundary condition testing
  • Clear test method naming conventions

airbnb/lottie-android

lottie/src/test/java/com/airbnb/lottie/LottieCompositionFactoryTest.java

            
package com.airbnb.lottie;

import com.airbnb.lottie.model.LottieCompositionCache;

import com.airbnb.lottie.parser.moshi.JsonReader;
import org.junit.Before;
import org.junit.Test;
import org.robolectric.RuntimeEnvironment;

import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
import static okio.Okio.buffer;
import static okio.Okio.source;
import static org.junit.Assert.assertNotEquals;

import okio.Source;

@SuppressWarnings("ReferenceEquality")
public class LottieCompositionFactoryTest extends BaseTest {
    private static final String JSON = "{\"v\":\"4.11.1\",\"fr\":60,\"ip\":0,\"op\":180,\"w\":300,\"h\":300,\"nm\":\"Comp 1\",\"ddd\":0,\"assets\":[]," +
            "\"layers\":[{\"ddd\":0,\"ind\":1,\"ty\":4,\"nm\":\"Shape Layer 1\",\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":0," +
            "\"k\":0,\"ix\":10},\"p\":{\"a\":0,\"k\":[150,150,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100,100]," +
            "\"ix\":6}},\"ao\":0,\"shapes\":[{\"ty\":\"rc\",\"d\":1,\"s\":{\"a\":0,\"k\":[100,100],\"ix\":2},\"p\":{\"a\":0,\"k\":[0,0],\"ix\":3}," +
            "\"r\":{\"a\":0,\"k\":0,\"ix\":4},\"nm\":\"Rectangle Path 1\",\"mn\":\"ADBE Vector Shape - Rect\",\"hd\":false},{\"ty\":\"fl\"," +
            "\"c\":{\"a\":0,\"k\":[0.928262987324,0,0,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector " +
            "Graphic - Fill\",\"hd\":false}],\"ip\":0,\"op\":180,\"st\":0,\"bm\":0}]}";

    private static final String NOT_JSON = "not json";

    @Before
    public void setup() {
        LottieCompositionCache.getInstance().clear();
        LottieCompositionCache.getInstance().resize(20);
    }

    @Test
    public void testLoadJsonString() {
        LottieResult<LottieComposition> result = LottieCompositionFactory.fromJsonStringSync(JSON, "json");
        assertNull(result.getException());
        assertNotNull(result.getValue());
    }

  @Test
  public void testLoadJsonStringHitsCache() {
    LottieResult<LottieComposition> result1 = LottieCompositionFactory.fromJsonStringSync(JSON, "json");
    LottieResult<LottieComposition> result2 = LottieCompositionFactory.fromJsonStringSync(JSON, "json");
    assertEquals(result1, result2);
  }

  @Test
  public void testLoadDifferentJsonStringsDoesntHitsCache() {
    LottieResult<LottieComposition> result1 = LottieCompositionFactory.fromJsonStringSync(JSON, "jso1");
    LottieResult<LottieComposition> result2 = LottieCompositionFactory.fromJsonStringSync(JSON, "json2");
    assertNotEquals(result1, result2);
  }

    @Test
    public void testLoadInvalidJsonString() {
        LottieResult<LottieComposition> result = LottieCompositionFactory.fromJsonStringSync(NOT_JSON, "not_json");
        assertNotNull(result.getException());
        assertNull(result.getValue());
    }

    @Test
    public void testLoadJsonSource() {
        Source source = source(new ByteArrayInputStream(JSON.getBytes()));
        LottieResult<LottieComposition> result = LottieCompositionFactory.fromJsonSourceSync(source, "json");
        assertNull(result.getException());
        assertNotNull(result.getValue());
    }

    @Test
    public void testLoadJsonReader() {
        JsonReader reader = JsonReader.of(buffer(source(new ByteArrayInputStream(JSON.getBytes()))));
        LottieResult<LottieComposition> result = LottieCompositionFactory.fromJsonReaderSync(reader, "json");
        assertNull(result.getException());
        assertNotNull(result.getValue());
    }

    @Test
    public void testLoadInvalidJsonReader() {
        JsonReader reader = JsonReader.of(buffer(source(new ByteArrayInputStream(NOT_JSON.getBytes()))));
        LottieResult<LottieComposition> result = LottieCompositionFactory.fromJsonReaderSync(reader, "json");
        assertNotNull(result.getException());
        assertNull(result.getValue());
    }

    @Test
    public void testLoadInvalidAssetName() {
        LottieResult<LottieComposition> result = LottieCompositionFactory.fromAssetSync(RuntimeEnvironment.application, "square2.json");
        assertEquals(FileNotFoundException.class, result.getException().getClass());
        assertNull(result.getValue());
    }

    @Test
    public void testNonJsonAssetFile() {
        LottieResult<LottieComposition> result = LottieCompositionFactory.fromAssetSync(RuntimeEnvironment.application, "not_json.txt");
        assertNotNull(result.getException());
        assertNull(result.getValue());
    }

    @Test
    public void testLoadInvalidRawResName() {
        LottieResult<LottieComposition> result = LottieCompositionFactory.fromRawResSync(RuntimeEnvironment.application, 0);
        assertNotNull(result.getException());
        assertNull(result.getValue());
    }

    @Test
    public void testNullMultipleTimesAsync() {
        JsonReader reader = JsonReader.of(buffer(source(getNeverCompletingInputStream())));
        LottieTask<LottieComposition> task1 = LottieCompositionFactory.fromJsonReader(reader, null);
        LottieTask<LottieComposition> task2 = LottieCompositionFactory.fromJsonReader(reader, null);
        assertNotSame(task1, task2);
    }

    @Test
    public void testNullMultipleTimesSync() {
        JsonReader reader = JsonReader.of(buffer(source(getNeverCompletingInputStream())));
        LottieResult<LottieComposition> task1 = LottieCompositionFactory.fromJsonReaderSync(reader, null);
        LottieResult<LottieComposition> task2 = LottieCompositionFactory.fromJsonReaderSync(reader, null);
        assertNotSame(task1, task2);
    }

    @Test
    public void testZeroCacheWorks() {
        JsonReader reader = JsonReader.of(buffer(source(getNeverCompletingInputStream())));
        LottieCompositionFactory.setMaxCacheSize(1);
        LottieResult<LottieComposition> taskFoo1 = LottieCompositionFactory.fromJsonReaderSync(reader, "foo");
        LottieResult<LottieComposition> taskFoo2 = LottieCompositionFactory.fromJsonReaderSync(reader, "foo");
        assertNotSame(taskFoo1, taskFoo2);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testCannotSetCacheSizeToZero() {
        LottieCompositionFactory.setMaxCacheSize(0);
    }

    private static InputStream getNeverCompletingInputStream() {
        return new InputStream() {
            @Override public int read() throws IOException {
                return 100;
            }
        };
    }
}