Skip to content

Particle Effects Component Test Plan

Kinsey Dodd edited this page Oct 6, 2026 · 1 revision

Overview

This test plan outlines comprehensive testing strategies for the ParticleEffectsRenderingComponent class, covering functionality, integration, performance, and edge cases.


Test Objectives

  1. Verify correct particle rendering for each effect type (fire, ice, poison)
  2. Ensure proper state management and component lifecycle
  3. Validate color gradients and visual interpolation
  4. Test integration with status components
  5. Verify lighting system integration
  6. Confirm resource cleanup and disposal
  7. Test edge cases and error handling
  8. Assess performance with multiple active effects

Test Environment

  • Framework: JUnit 4 or JUnit 5
  • Mocking Library: Mockito
  • Game Framework: LibGDX Test Fixtures
  • Build Tool: Gradle or Maven

Unit Tests

1. Initialization and Lifecycle Tests

Test: testCreateWithAllComponents()

Objective: Verify component retrieves all stat components during creation.

Setup:

  • Create entity with BurnStatsComponent, SlowStatsComponent, PoisonStatsComponent, and PointLightComponent
  • Create ParticleEffectsRenderingComponent

Expected Result:

  • All component references are non-null
  • Point light component is successfully retrieved

Code Sketch:

@Test
public void testCreateWithAllComponents() {
  Entity entity = new Entity();
  entity.addComponent(new BurnStatsComponent());
  entity.addComponent(new SlowStatsComponent());
  entity.addComponent(new PoisonStatsComponent());
  entity.addComponent(new PointLightComponent());
  
  ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
  entity.addComponent(component);
  component.create();
  
  assertNotNull(component.burnStats);
  assertNotNull(component.slowStats);
  assertNotNull(component.poisonStats);
  assertNotNull(component.pointLightComponent);
}

Test: testCreateWithMissingComponents()

Objective: Verify component handles missing stat components gracefully.

Setup:

  • Create entity with no status components

Expected Result:

  • Component creation succeeds
  • All stat component references are null
  • No exception is thrown

Code Sketch:

@Test
public void testCreateWithMissingComponents() {
  Entity entity = new Entity();
  ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
  entity.addComponent(component);
  
  assertDoesNotThrow(() -> component.create());
  assertNull(component.burnStats);
  assertNull(component.slowStats);
  assertNull(component.poisonStats);
}

Test: testCreateWithoutPointLight()

Objective: Verify component handles missing point light component.

Setup:

  • Create entity with status components but no PointLightComponent

Expected Result:

  • Component creation succeeds
  • Point light reference is null
  • No exception during draw call

Code Sketch:

@Test
public void testCreateWithoutPointLight() {
  Entity entity = new Entity();
  entity.addComponent(new BurnStatsComponent());
  ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
  entity.addComponent(component);
  
  assertDoesNotThrow(() -> component.create());
  assertNull(component.pointLightComponent);
}

2. Drawing and State Tests

Test: testDrawWithNoActiveEffects()

Objective: Verify component skips rendering when no effects are active.

Setup:

  • Mock BurnStatsComponent, SlowStatsComponent, PoisonStatsComponent to return false for active flags
  • Mock GameTime and ServiceLocator
  • Mock SpriteBatch

Expected Result:

  • No shapes are rendered
  • Point light is cleared
  • Early return occurs

Code Sketch:

@Test
public void testDrawWithNoActiveEffects() {
  ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
  component.burnStats = mock(BurnStatsComponent.class);
  component.slowStats = mock(SlowStatsComponent.class);
  component.poisonStats = mock(PoisonStatsComponent.class);
  component.pointLightComponent = mock(PointLightComponent.class);
  
  when(component.burnStats.isBurning()).thenReturn(false);
  when(component.slowStats.isSlowed()).thenReturn(false);
  when(component.poisonStats.isPoisoned()).thenReturn(false);
  
  SpriteBatch batch = mock(SpriteBatch.class);
  component.draw(batch);
  
  verify(component.pointLightComponent).setColor(Color.CLEAR);
}

Test: testDrawWithBurningEffect()

Objective: Verify fire particles render when entity is burning.

Setup:

  • Mock BurnStatsComponent to return true for isBurning()
  • Mock GameTime to return valid time
  • Mock entity position and scale

Expected Result:

  • Fire effect is drawn
  • Correct fire colors are used
  • Point light is set to fire base color

Code Sketch:

@Test
public void testDrawWithBurningEffect() {
  ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
  component.burnStats = mock(BurnStatsComponent.class);
  component.slowStats = mock(SlowStatsComponent.class);
  component.poisonStats = mock(PoisonStatsComponent.class);
  component.pointLightComponent = mock(PointLightComponent.class);
  
  when(component.burnStats.isBurning()).thenReturn(true);
  when(component.slowStats.isSlowed()).thenReturn(false);
  when(component.poisonStats.isPoisoned()).thenReturn(false);
  
  // Mock entity and GameTime
  Entity entity = mock(Entity.class);
  when(entity.getCenterPosition()).thenReturn(new Vector2(10, 10));
  when(entity.getScale()).thenReturn(new Vector2(1, 1));
  
  // Setup component with entity
  component.entity = entity;
  
  SpriteBatch batch = mock(SpriteBatch.class);
  component.draw(batch);
  
  // Verify fire base color was set
  verify(component.pointLightComponent).setColor(ParticleEffectsRenderingComponent.fireBaseColour);
}

Test: testDrawWithSlowedEffect()

Objective: Verify ice particles render when entity is slowed.

Setup:

  • Mock SlowStatsComponent to return true for isSlowed()

Expected Result:

  • Ice effect is drawn with correct colors
  • Point light is set to ice base color

Test: testDrawWithPoisonedEffect()

Objective: Verify poison particles render when entity is poisoned.

Setup:

  • Mock PoisonStatsComponent to return true for isPoisoned()

Expected Result:

  • Poison effect is drawn with correct colors
  • Point light is set to poison base color

Test: testDrawWithMultipleEffects()

Objective: Verify all three effects render simultaneously.

Setup:

  • All status components return true

Expected Result:

  • All three effects are drawn
  • Point light is set to last effect's base color (poison)
  • No exceptions occur

Code Sketch:

@Test
public void testDrawWithMultipleEffects() {
  ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
  component.burnStats = mock(BurnStatsComponent.class);
  component.slowStats = mock(SlowStatsComponent.class);
  component.poisonStats = mock(PoisonStatsComponent.class);
  component.pointLightComponent = mock(PointLightComponent.class);
  
  when(component.burnStats.isBurning()).thenReturn(true);
  when(component.slowStats.isSlowed()).thenReturn(true);
  when(component.poisonStats.isPoisoned()).thenReturn(true);
  
  Entity entity = mock(Entity.class);
  component.entity = entity;
  when(entity.getCenterPosition()).thenReturn(new Vector2(10, 10));
  when(entity.getScale()).thenReturn(new Vector2(1, 1));
  
  SpriteBatch batch = mock(SpriteBatch.class);
  assertDoesNotThrow(() -> component.draw(batch));
}

Test: testDrawWithNullGameTime()

Objective: Verify component handles missing GameTime gracefully.

Setup:

  • Mock ServiceLocator.getTimeSource() to return null

Expected Result:

  • Early return without rendering
  • No exception thrown

Code Sketch:

@Test
public void testDrawWithNullGameTime() {
  ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
  
  try (MockedStatic<ServiceLocator> mockedServiceLocator = mockStatic(ServiceLocator.class)) {
    mockedServiceLocator.when(ServiceLocator::getTimeSource).thenReturn(null);
    
    SpriteBatch batch = mock(SpriteBatch.class);
    assertDoesNotThrow(() -> component.draw(batch));
  }
}

3. Color Gradient Tests

Test: testColourGradientLowerHalf()

Objective: Verify color interpolation in lower half of gradient (base to mid).

Setup:

  • Call colourGradient() with t values between 0 and 0.5
  • Use fire colors: base (red), mid (orange), tip (yellow)

Expected Result:

  • For t=0: output color equals base color
  • For t=0.25: output is between base and mid
  • For t=0.5: output color equals mid color

Code Sketch:

@Test
public void testColourGradientLowerHalf() {
  Color base = new Color(0.55f, 0.05f, 0.02f, 1f); // Fire base (red)
  Color mid = new Color(0.95f, 0.35f, 0.05f, 1f);  // Fire mid (orange)
  Color tip = new Color(1f, 0.9f, 0.3f, 1f);       // Fire tip (yellow)
  Color out = new Color();
  
  // Test t = 0
  ParticleEffectsRenderingComponent.colourGradient(base, mid, tip, 0f, out);
  assertEquals(base.r, out.r, 0.001f);
  assertEquals(base.g, out.g, 0.001f);
  assertEquals(base.b, out.b, 0.001f);
  
  // Test t = 0.5
  ParticleEffectsRenderingComponent.colourGradient(base, mid, tip, 0.5f, out);
  assertEquals(mid.r, out.r, 0.001f);
  assertEquals(mid.g, out.g, 0.001f);
  assertEquals(mid.b, out.b, 0.001f);
}

Test: testColourGradientUpperHalf()

Objective: Verify color interpolation in upper half of gradient (mid to tip).

Setup:

  • Call colourGradient() with t values between 0.5 and 1.0

Expected Result:

  • For t=0.5: output color equals mid color
  • For t=0.75: output is between mid and tip
  • For t=1.0: output color equals tip color

Code Sketch:

@Test
public void testColourGradientUpperHalf() {
  Color base = new Color(0.55f, 0.05f, 0.02f, 1f);
  Color mid = new Color(0.95f, 0.35f, 0.05f, 1f);
  Color tip = new Color(1f, 0.9f, 0.3f, 1f);
  Color out = new Color();
  
  // Test t = 1.0
  ParticleEffectsRenderingComponent.colourGradient(base, mid, tip, 1f, out);
  assertEquals(tip.r, out.r, 0.001f);
  assertEquals(tip.g, out.g, 0.001f);
  assertEquals(tip.b, out.b, 0.001f);
}

Test: testColourGradientAllColorPalettes()

Objective: Verify color gradient works correctly for all effect types.

Setup:

  • Test fire, ice, and poison color palettes

Expected Result:

  • Each palette produces smooth gradients
  • No out-of-range color values

4. Z-Index Tests

Test: testGetZIndex()

Objective: Verify Z-index is offset above parent component.

Setup:

  • Mock parent RenderComponent to return Z-index of 5.0f

Expected Result:

  • Component returns 5.01f (parent + 0.01f)

Code Sketch:

@Test
public void testGetZIndex() {
  ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
  
  // Mock parent getZIndex
  float parentZIndex = 5.0f;
  float expectedZIndex = parentZIndex + 0.01f;
  
  // This would require refactoring to make testable, or using reflection
  float actualZIndex = component.getZIndex();
  assertEquals(expectedZIndex, actualZIndex, 0.001f);
}

5. Disposal Tests

Test: testDispose()

Objective: Verify resources are properly cleaned up.

Setup:

  • Create component with all sub-components

Expected Result:

  • PointLightComponent.dispose() is called (if present)
  • ShapeRenderer.dispose() is called
  • No exceptions occur

Code Sketch:

@Test
public void testDispose() {
  ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
  component.pointLightComponent = mock(PointLightComponent.class);
  
  assertDoesNotThrow(() -> component.dispose());
  verify(component.pointLightComponent).dispose();
}

Test: testDisposeWithoutPointLight()

Objective: Verify disposal works when point light is null.

Setup:

  • Component without point light

Expected Result:

  • No exception thrown
  • Disposal completes successfully

Integration Tests

1. Full Workflow Tests

Test: testCompleteLifecycleWithBurning()

Objective: Test full lifecycle: create → draw → dispose.

Setup:

  • Create entity with all components
  • Initialize component
  • Call draw multiple times
  • Dispose

Expected Result:

  • All operations complete without error
  • Visual effect is consistent across draw calls

Test: testEffectStateTransitions()

Objective: Test transitions between effect states.

Setup:

  • Start with no effects
  • Activate burning
  • Add slowness (while burning)
  • Remove burning
  • Verify only slowness is active

Expected Result:

  • Visual effects update correctly at each transition
  • No flickering or artifacts

Test: testLightingIntegration()

Objective: Verify lighting system integrates correctly.

Setup:

  • Entity with PointLightComponent
  • Activate various effects

Expected Result:

  • Light color matches effect base color
  • Light is cleared when effects are inactive

2. Component Interaction Tests

Test: testInteractionWithMultipleRenderComponents()

Objective: Verify component works alongside other render components.

Setup:

  • Entity with multiple render components
  • Include ParticleEffectsRenderingComponent

Expected Result:

  • All components render correctly
  • Z-order is respected
  • No rendering conflicts

Performance Tests

1. Rendering Performance

Test: testRenderPerformanceWithSingleEntity()

Objective: Measure frame time with one active effect.

Setup:

  • Single entity with one effect type
  • Run for 60 frames
  • Measure average frame time

Expected Result:

  • Average frame time < 5ms

Code Sketch:

@Test
public void testRenderPerformanceWithSingleEntity() {
  ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
  // Setup component...
  
  long startTime = System.nanoTime();
  for (int i = 0; i < 60; i++) {
    component.draw(mockBatch);
  }
  long endTime = System.nanoTime();
  
  double avgFrameTimeMs = (endTime - startTime) / 1_000_000.0 / 60.0;
  assertTrue("Average frame time should be < 5ms", avgFrameTimeMs < 5.0);
}

Test: testRenderPerformanceWithMultipleEffects()

Objective: Measure frame time with all three effects active.

Setup:

  • Entity with all three effects
  • Run for 60 frames

Expected Result:

  • Average frame time < 7ms

Test: testRenderPerformanceWithMultipleEntities()

Objective: Measure frame time with 10+ entities all with effects.

Setup:

  • 10 entities, each with effects
  • Run for 60 frames

Expected Result:

  • Average frame time remains acceptable
  • No memory leaks

2. Memory Tests

Test: testMemoryLeaksInDrawCycle()

Objective: Verify no memory leaks in repeated draw cycles.

Setup:

  • Call draw 10,000 times
  • Monitor memory usage
  • Force garbage collection

Expected Result:

  • Heap size remains stable
  • No OutOfMemory errors

Edge Case Tests

1. Boundary Tests

Test: testWithZeroBaseSize()

Objective: Verify component handles zero entity scale.

Setup:

  • Entity with scale (0, 0)

Expected Result:

  • No exceptions
  • Minimal or no particles rendered

Test: testWithVeryLargeScale()

Objective: Verify component handles very large entity scale.

Setup:

  • Entity with scale (1000, 1000)

Expected Result:

  • Particles scale appropriately
  • No performance degradation
  • Particles remain visible

Test: testWithNegativeScale()

Objective: Verify component handles negative scale values.

Setup:

  • Entity with negative scale (-1, -1)

Expected Result:

  • Component handles gracefully
  • No visual artifacts

2. Timing Tests

Test: testWithVeryHighGameTime()

Objective: Verify animations work correctly at high time values.

Setup:

  • GameTime returns very large values (e.g., 1,000,000 seconds)

Expected Result:

  • Animations continue to loop correctly
  • No numerical precision issues

Test: testWithZeroGameTime()

Objective: Verify component handles zero elapsed time.

Setup:

  • GameTime returns 0

Expected Result:

  • Initial particle state is correct
  • No division by zero errors

3. Position Tests

Test: testWithEntityAtOrigin()

Objective: Verify rendering at world origin.

Setup:

  • Entity center at (0, 0)

Expected Result:

  • Particles render correctly at origin

Test: testWithEntityAtNegativePosition()

Objective: Verify rendering at negative coordinates.

Setup:

  • Entity center at (-100, -100)

Expected Result:

  • Particles render correctly at negative positions

Visual/Regression Tests

1. Visual Appearance

Test: testFireEffectAppearance()

Objective: Manual visual verification of fire effect.

Setup:

  • Render fire effect to screen

Expected Result:

  • Flames rise upward
  • Colors transition from red → orange → yellow
  • Effect sways gently
  • Flickering is visible and organic

Test: testIceEffectAppearance()

Objective: Manual visual verification of ice effect.

Expected Result:

  • Particles rise upward
  • Colors transition from blue → cyan → pale white
  • Effect is distinct from fire

Test: testPoisonEffectAppearance()

Objective: Manual visual verification of poison effect.

Expected Result:

  • Particles rise upward
  • Colors transition from purple → violet → greenish-yellow
  • Effect is distinct from fire and ice

2. Animation Smoothness

Test: testAnimationContinuity()

Objective: Verify animations are smooth across frames.

Setup:

  • Render 300 frames (5 seconds at 60 FPS)
  • Record frame-to-frame transitions

Expected Result:

  • No frame skips or stutters
  • Motion is smooth and continuous
  • No popping or sudden changes

Test Data and Fixtures

Mock Objects

  • BurnStatsComponent
  • SlowStatsComponent
  • PoisonStatsComponent
  • PointLightComponent
  • Entity
  • GameTime
  • SpriteBatch
  • ShapeRenderer

Test Constants

  • Standard entity position: (0, 0)
  • Standard entity scale: (1, 1)
  • Test game time: 1000 ms (1 second)
  • Multiple entity count: 10

Test Coverage Goals

Component Target Coverage
create() 100%
draw() 95%
drawEffect() 90%
colourGradient() 100%
getZIndex() 100%
dispose() 100%
Overall 95%

Continuous Integration

CI Pipeline Stages

  1. Unit Tests: All unit tests must pass
  2. Integration Tests: Integration tests must pass
  3. Performance Tests: Frame time benchmarks within tolerance
  4. Code Coverage: Minimum 95% coverage
  5. Visual Regression: Compare screenshots (optional)

Test Execution Command

gradle test --tests ParticleEffectsRenderingComponentTest

Known Limitations and Future Considerations

  1. Deterministic Randomness: Tests using MathUtils.random() may be non-deterministic. Consider seeding for reproducibility.

  2. LibGDX Dependencies: Tests may require libgdx-backend-headless or equivalent for CI environments.

  3. Visual Regression: Manual visual tests may require screenshot comparison tools or human review.

  4. Performance Baselines: Performance test thresholds should be calibrated for target hardware.

  5. Thread Safety: Not tested; component assumes single-threaded execution.


Test Execution Schedule

  • Unit Tests: Run on every commit
  • Integration Tests: Run on every commit
  • Performance Tests: Run daily or before release
  • Visual Tests: Run before release
  • Full Suite: Run before version release

Clone this wiki locally