Skip to content

Parallel execution and @TestInstance(PER_CLASS) result in concurrency issues for instance fields mutated in test and lifecycle methods #1511

@dlanaghen

Description

@dlanaghen

@TestInstance annotation seems to cause threading problems with tests in Spring Boot.

<junit5.jupiter.version>5.3.0-M1</junit5.jupiter.version>
<junit5.platform.version>1.2.0</junit5.platform.version>

I was able to replicate this problem by creating a small Spring Boot application using the Spring Initializer (https://start.spring.io/).

The application:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

A dummy class for testing:

package com.example.demo;

public class Klass {
    public Klass(String field) {
        this.field = field;
    }

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    String field;
}

My test class:

package com.example.demo;

import org.junit.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringRunner;

// @TestInstance(TestInstance.Lifecycle.PER_CLASS) // Needed for BeforeAll annotation but causes threading issues. 
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {DemoApplication.class})
public class DemoApplicationTests {

	@Test
	public void contextLoads() {
	}

	Klass klass = null;

	@BeforeEach
	public void beforeEach() {
		klass = new Klass(Thread.currentThread().getName());
	}

	@RepeatedTest(8)
	public void parallel() {
		klass.setField("Modified: " + klass.getField());
	}

	@AfterEach
	public void afterEach() {
		System.out.println(String.format("%s - %s", Thread.currentThread().getName(), klass.field ));
	}
}

Output when not using @TestInstance:

ForkJoinPool-1-worker-3 - Modified: ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-0 - Modified: ForkJoinPool-1-worker-0
ForkJoinPool-1-worker-2 - Modified: ForkJoinPool-1-worker-2
ForkJoinPool-1-worker-3 - Modified: ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-4 - Modified: ForkJoinPool-1-worker-4
ForkJoinPool-1-worker-4 - Modified: ForkJoinPool-1-worker-4
ForkJoinPool-1-worker-6 - Modified: ForkJoinPool-1-worker-6
ForkJoinPool-1-worker-2 - Modified: ForkJoinPool-1-worker-2

Output when using @TestInstance:

ForkJoinPool-1-worker-0 - Modified: ForkJoinPool-1-worker-4 <<< oops
ForkJoinPool-1-worker-2 - Modified: ForkJoinPool-1-worker-4 <<<
ForkJoinPool-1-worker-3 - Modified: ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-0 - Modified: ForkJoinPool-1-worker-0
ForkJoinPool-1-worker-4 - Modified: ForkJoinPool-1-worker-0 <<<
ForkJoinPool-1-worker-2 - Modified: ForkJoinPool-1-worker-2
ForkJoinPool-1-worker-3 - Modified: ForkJoinPool-1-worker-3

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions