Summary: When a class has a @Cacheable
method, accessing that class's instance variable(s) in a test via reflection causes NoSuchFieldException.
Environment: Tested with versions 2.7.5 and 3.1.1 of spring-boot-starter-parent.
$ java -version
openjdk version "17.0.4" 2022-07-19 LTS
OpenJDK Runtime Environment Corretto-17.0.4.8.1 (build 17.0.4+8-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.4.8.1 (build 17.0.4+8-LTS, mixed mode, sharing)
$ mvn -v
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.4, vendor: Amazon.com Inc., runtime: /home/erik/.sdkman/candidates/java/17.0.4-amzn
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "5.19.0-46-generic", arch: "amd64", family: "unix"
How to recreate:
- Generate a Spring Boot project with Spring initializr, with Maven as build tool.
- On the main class, add @EnableCaching
and @Configuration
.
- In the same file as that of the main class, add the following code:
@Service
class MyService {
int x = 1;
@Cacheable("numbers")
public List<Integer> somethingSlow() {
try { Thread.sleep(5000); } catch (InterruptedException e) {}
return List.of(1,2,3);
}
}
- Modify the test file (generated by Spring initializr) to contain this code:
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class CachingNosuchfieldApplicationTests {
@Autowired MyService myService;
@BeforeAll
void setup() throws Exception {
myService.getClass().getDeclaredField("x");
}
@Test void someTest() {
var l = myService.somethingSlow();
var start = System.currentTimeMillis();
l = myService.somethingSlow();
var stop = System.currentTimeMillis();
assertTrue(stop - start < 100);
}
}
- Run the test with
mvn test
Expexted result: Test runs and passes.
Actual result: Test is ignored due to NoSuchFieldException
:
```java.lang.NoSuchFieldException: x
at java.base/java.lang.Class.getDeclaredField
**Observations:**
- If `@Cacheable` is removed, the test runs (and fails due to the assertion failing).
- If the call to `getDeclaredField` is removed, the test runs and passes.
- If `@TestInstance(...)` is removed, `setup` is made static, and `myService.getClass()` is changed to `MyService.class`, the test runs and passes.
**Comment From: snicoll**
I don't understand what you're doing. First of all, I can't reproduce what you've described and that's a general problem with sample in code blocks rather than a link to a GitHub repo that we can't run.
That said, `@Cacheable` creates a CGLIB proxy for your component so `getClass` returns that, rather than the service class. You can use `AopUtils#getTargetClass` to retrieve the underlying user class from a proxy.
**Comment From: erikv85**
Repo created. Recreate instructions:
git clone git@github.com:erikv85/caching-nosuchfield.git mvn -f caching-nosuchfield test ```