Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Second call to "when" fails when mocking a Spring @Cacheable #1822

Open
Oduig opened this issue Nov 11, 2019 · 9 comments
Open

Second call to "when" fails when mocking a Spring @Cacheable #1822

Oduig opened this issue Nov 11, 2019 · 9 comments

Comments

@Oduig
Copy link

Oduig commented Nov 11, 2019

Using Mockito 2.23.0 with Spring Boot 2.0.3, we cannot get Mockito to work with Spring's @Cacheable annotation in an integration test.

Class under test

@Component
public class MyClass {
  @Cacheable
  public String mockMe(String message) {
    return message;
  }
}

Test configuration

  @Configuration
  public static class TestConfiguration {
    @Bean
    public MyClass myClass() {
      return Mockito.mock(MyClass.class);
    }
  }

Test class

@Autowire
private MyClass myClass;

@Test
public void mockMe_test1() {
  when(myClass.mockMe(eq("Hello")).thenReturn("Mock response 1");
}
@Test
public void mockMe_test2() {
  when(myClass.mockMe(eq("Goodbye"))).thenReturn("Mock response 2");
}

What do you expect to happen?
I expect both calls to when to succeed.

What actually happens?
Both tests are green when ran individually. When ran together, the first test succeeds and the second call fails.

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: (...)
You cannot use argument matchers outside of verification or stubbing.

Without @Cacheable on MyClass, both tests succeed. I suspect that Cacheable "caches" the call to when, making the resulting object non-mockable when called a second time. How do we remedy this?

Let me know if I should put a full example up on GitHub. I first want to check if this is a known issue or if we are doing anything wrong on our end.

@TimvdLippe
Copy link
Contributor

Could you create a minimal example and open a PR with a failing test? Preferably without any external dependencies.

@Oduig
Copy link
Author

Oduig commented Nov 25, 2019

Hi, here is a minimal example.

https://github.com/Oduig/mockito-cacheable

@TimvdLippe
Copy link
Contributor

The minimal example has a large amount of dependencies (e.g. the Spring boot eco-system). Is there anything that prevents you from making a minimal example without any external dependencies? Debugging Spring Boot and its semantics would significantly increase our time investment to be able to determine whether Mockito is in the wrong or if the issue is in Spring Boot. If you can provide a minimal example with just Mockito, we know for certain Mockito is wrong and we can fix it. If however you can only reproduce with Spring Boot, it is likely an issue in Spring Boot instead.

@Oduig
Copy link
Author

Oduig commented Nov 26, 2019

I understand that Spring has a lot of dependencies, but as described in the title, @Cacheable is a Spring annotation. It surrounds a class method with a proxy which, when called a second time, diverts to a cached value rather than calling the real implementation. No doubt, that is the cause of the issue: the second call to Mockito.when sees the cached value instead of the real method and fails because it expected a method.

How could I demonstrate a problem with Spring + Mockito, without using Spring?

@TimvdLippe
Copy link
Contributor

You can copy the annotation and processing logic and remove all implementation details until you have a minimal example that fails. We have an extensive set of bugs test that are the minimal examples of the original issues (links to the original issues are usually in the source code): https://github.com/mockito/mockito/tree/release/3.x/src/test/java/org/mockitousage/bugs

For example, https://github.com/mockito/mockito/blob/release/3.x/src/test/java/org/mockitousage/bugs/FinalHashCodeAndEqualsRaiseNPEInInitMocksTest.java is the distilled version of #327 which included a lot of code originally.

@Oduig
Copy link
Author

Oduig commented Nov 27, 2019

I've always been very reasonable about my open source contributions, making an effort to help resolve issues. Extracting pieces of a framework into an individually compilable and working unit is not something that I've ever encountered as a requirement for a bug report. I honestly think you are asking for too much, making me jump through hoops just to prove my issue is valid, when I think I've already demonstrated that. The burden of debugging should not be on the one reporting the issue.

@mrattanaxay
Copy link

@Oduig Did you find a workaround ? If so, could you share you solution ? I'm facing almost the same problem, trying to test my cacheconfiguration is working.

@Oduig
Copy link
Author

Oduig commented Feb 24, 2022

@mrattanaxay I don't think we ever solved the issue completely, and it was in a previous job so I cannot check the code base today. I think we settled on testing the class alongside its usage site, so that the @Cacheable annotation is not on the outer layer that is mocked.

For a generic solution, what I expect would work is making a class like MyClassTestDelegate which mirrors the interface of MyClass and delegates all methods to a member private MyClass delegate;. This essentialy moves the problem one class layer down. You can then mock the delegate class instead.

@wojarskischneider
Copy link

wojarskischneider commented Jan 5, 2023

Create an interface eg.

interface IMyClass {
  public String mockMe(String message) {

then use just interface signature where needed in tests to create mocks:
Mockito.mock(IMyClass.class);

then you can use @Cacheable wherever you desire in the actual implementation.

class MyClass implements IMyClass {
  @Cacheable
  public String mockMe(String message) {

worked for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants