jmockit / jmockit1

Advanced Java library for integration testing, mocking, faking, and code coverage
Other
458 stars 238 forks source link

[question] how to mock reactive stack #700

Closed bilak closed 3 years ago

bilak commented 3 years ago

Please provide the following information:

The test sample is here

Saljack commented 3 years ago

It is a bug. I tested it with @Capturing DemoRepositoryImpl demoRepository and it works without any problem. So I tried to add times =1 into the Expectations.

new Expectations() {{
  demoRepository.testMethodOne();
  result = Mono.just("monoone");
  times = 1;

  demoRepository.testMethodTwo();
  result = Mono.just("monotwo");
  times = 1;
}};

and changed an implementation in the method callMonoMethods to:

@Override
public String callMonoMethods() {
  Mono<String> one = demoRepository.testMethodOne();
  Mono<String> two = demoRepository.testMethodTwo();
  return Mono.zip(one, two)
    .map(tuple -> tuple.getT1() + tuple.getT2())
    .block();
}

and then an exception is thrown:

Unexpected invocation to:
com.example.demo.data.repository.CustomDemoRepository#testMethodOne()

but it is from a call of testMethodTwo

rliesenfeld commented 3 years ago

Can't reproduce the failure without the code under test.

bilak commented 3 years ago

Can't reproduce the failure without the code under test.

@rliesenfeld What you mean by that? Did you run the tests and they are working properly?

rliesenfeld commented 3 years ago

Ah ok, now I see all the code is under https://github.com/bilak-poc/jmockit-reactive.

rliesenfeld commented 3 years ago

I get org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [com.example.demo.data.repository.DemoRepository demoRepository] in method [void com.example.demo.DemoApplicationTests.testMono(com.example.demo.data.repository.DemoRepository)].

Which, of course, occurs because JMockit isn't getting initialized, due to missing configuration in the pom.xml.

bilak commented 3 years ago

let me fix the example, maybe I just copied something wrong from our application

rliesenfeld commented 3 years ago

With a simplified version of the code which still uses the Reactive API (Mono), it works fine for me:

final class DemoApplicationTests {
    @Tested TestService testService;
    @Injectable DemoRepository demoRepository;

    @Test
    void testMono() {
        new Expectations() {{
            demoRepository.getOne(); result = Mono.just("monoone");
            demoRepository.getTwo(); result = Mono.just("monotwo");
        }};

        var result = testService.callMonoMethods();

        assertEquals("monoonemonotwo", result);
    }
}

@Service
public final class TestService {
    @Autowired private DemoRepository demoRepository;

    public String callMonoMethods() {
        return Mono.zip(demoRepository.getOne(), demoRepository.getTwo())
            .map(tuple -> tuple.getT1() + tuple.getT2())
            .block();
    }
}

public interface DemoRepository {
    Mono<String> getOne();
    Mono<String> getTwo();
}
bilak commented 3 years ago

@rliesenfeld I've fixed the config. Why then it's not working with my configuration? We are using mainly integration tests where we are trying to use @Capturing parameter only for some objects through method parameter. If we use @Capturing as a test class field then we have other problems.

Saljack commented 3 years ago

I think the problem is with combination of Spring Data and JMockit. Spring Data generates Method implementation by JDK proxy.

rliesenfeld commented 3 years ago

Yes, it seems related to bytecode generated by the Spring framework. And @Capturing does not apply to dynamically generated classes.

In this case, the goal was to mock Repository interfaces in integration tests. I suggest to not mock them and just create actual integration tests.