I have a component that's using #Retryable annotation and another service using that component. So I'm trying to test that the component using #Retryable annotation is actually retrying.
I've tried every solution there is on the web right now but nothing worked for me. I'm trying to create a unit test for this and not integration test. So far I've managed to get to the exception that's supposed to be thrown and #Retryable wasn't even retrying, the method just threw the exception and thats it.
This is the component using Retryable annotation:
#Component
public class OurComponent {
#Retryable(maxAttempts = 10,
backoff = #Backoff(delay = 2000),
value = {someException.class}
)
public void someMethod(SomeObject someObject) throws someException {
Object createObject = anotherMethod(someObject); //this method throws someException
...
}
}
And the service using this ourComponent:
#Service
public class someService {
private final OurComponent ourComponent;
public SomeService(OurComponent ourComponent) {
this.ourComponent = ourComponent;
}
...
public void methodUsingComponent() {
SomeObject someObject = new SomeObject(args);
ourComponent.someMethod(someObject);
}
}
Now I've tried to #InjectMocks and #MockBean this service and component but it still didn't work. Is it even possible to test #Retryable annotation without doing integration test?
If you use a unit test that doesn't use spring at all, you won't be able to test it easily.
This is due to the fact that annotations like this are recognized by spring and the corresponding bean is wrapped with a runtime-generated proxy that implements the "retry" logic.
Now, if you don't have spring who triggers all this mechanism, this #Retryable annotation is basically useless, mockito doesn't know anything about, so is Junit.
You could try to create a proxy like this manually (check what logic spring-retry invokes) but it looks to be an overkill. And frankly speaking, it doesn't give you anything. Unit test should check the functionality of your code and not the logic behind spring retry that was implemented by somewhere else and tested.
Related
I have implemented Micrometer Prometheus counter in my service by injecting MeterRegistry and incrementing the count as shown below, and I have written a test case as well, but when I am running the test case, I am getting:
"java.lang.NullPointerException: Cannot invoke
"io.micrometer.core.instrument.MeterRegistry.counter(String,
String[])" because "this.meterRegistry" is null".
Service file:
#Autowired
private MeterRegistry meterRegistry;
public void counterIncrement() {
meterRegistry.counter("test_count").increment();
}
Test case file:
#MockBean
private MeterRegistry registry;
#Test
void testCounter() {
// invoking counterIncrement();
}
How do you create your class under test?
Since the registry is never instantiated, something seems up with how you setup your test.
Check that you are using the #MockBean in the correct way. This will replace the bean in the application context and if you do not spin up a spring context in your test, it will not work. See this post for more info.
A different approach would be to use #Mock and inject the registry in the constructor, example:
#ExtendWith(MockitoExtension.class)
public class MyServiceTest {
#Mock
private MeterRegistry registry;
private MyService myService;
#BeforeEach
void setup() {
myService = new MyService(registry);
}
#Test
void testCounter() {
var counter = mock(Counter.class);
given(registry.counter(any(String.class))).willReturn(counter);
myService.counterIncrement();
}
You can test metrics without Mockito using SimpleMeterRegistry
#Test
void testCounter() {
var meterRegistry = new SimpleMeterRegistry();
Metrics.addRegistry(meterRegistry);
// invoke counterIncrement();
var actual = meterRegistry.counter("test_count").count();
assertEquals(1.0d, actual);
}
Depending on which junit version you are using you need to add the annotation to your test class. Junit 5: #ExtendWith(MockitoExtension.class) or for Junit 4: #RunWith(MockitoJUnitRunner.class)
Depending on the test and the service there are several ways to deal with the missing MeterRegistry.
If you use a spring context in your test, try to use a test configuration to create the MeterRegistry bean.
If your test uses some Mock framework, you could mock the MeterRegistry as suggested by by #Hans-Christian.
If you simply make the member meterRegistry non-private. You could set it to a SimpleMeterRegistry in some setup method, anotated with #BeforeEach as suggested by #checketts in the comments.
If mocking the meter registry gets complicated, you could easily build and use some factory that provides the registry and mock this factory. A very easy factory will do, e.g. a spring #Component with an autowired MeterRegistry and some public getter for the factory.
You could use the factory method pattern as described in wikipedia to get the MeterRegistry, overwrite the factory method in a subclass of your service and use this subclass in the test. (Note that the gang of four did use a static factory method, you'll need a non-static method.)
I favour solution 3 but would use solution 1 whenever appropriate. I've added solutions 4 and 5 just because there might be some additional reasons and special cases that make these solutions a good choice. If so, I prefer 4 over 5.
We'are imlementing part of our security at service layer, so I add #PreAuthorize annotation to some methods of MyService.
At MyServiceSecurityTest I want to test only security role-permission matrix, without any business logic. For that reason I have to mock MyService. the problem is that both Mockito and Spring security use CGLIB proxies, and my service is not enhanced with #PreAuthorize after Mockito.mock(MyService.class).
Is there any way to mock service and preserve #PreAuthorize logic?
Example:
#Service
public class MyService implements IMyService {
#Override
#PreAuthorize("hasAuthority('SYSOP')")
public void someMethod(ComplexDTO dto) {
// lots of logic and dependencies, require lots of stubbing.
}
}
In order to avoid initialization of all dependencies of MyService#someMethod and building ComplexDTO at MyServiceSecurityTest I want to mock MyServiceSecurityTest but preserve #PreAuthorize checks.
You need to do integration tests and not unit tests. In general, you do not see mock classes in integration tests, at least you would not mock the class you are testing, in this I case I guess its the MyService class.
Setting up integration tests involves reading up on, but the short example below should get you on the right path
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("myProfile")
public class MyServiceIT {
private final Logger logger = LoggerFactory.getLogger(getClass());
#Autowired
private TestRestTemplate restTemplate;
#Test
public void testMyService() {
logger.info("testMyService");
//user TestRestTemplate to call your service.
}
}
EDIT: In this integration test, Spring boots up normally. That means all the annotations for security are processed and all the beans it needs to create are created and properly injected. One thing you may have to control is the Spring profile.... that can be done with the #ActiveProfiles("myProfile") annotation, which I just added to the example.
After help from experts over at question Camel unit test with cametestsupport, template is always null, I ended up with one more issue.
Below is my test class - a simple test that tests a route which has only ValidationProcessor POJO.
public class RouteTests extends CamelTestSupport {
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
#Override
public void configure() {
from("direct:start")
.filter().method(ValidationProcessor.class, "validate")
.to("mock:result");
}
};
}
#Test
public void testSendMatchingMessage() throws Exception {
ObjectMapper objectmapper = new ObjectMapper();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream stream = loader.getResourceAsStream("test.json");
JSONObject testJson = new JSONObject(objectmapper.readValue(stream, Map.class));
MockEndpoint resultEndpoint = getMockEndpoint("mock:result");
resultEndpoint.expectedMessageCount(1);
template.sendBody("direct:start", testJson);
resultEndpoint.assertIsSatisfied();
}
}
The problem comes when this ValidationProcessor has an #Autowired component in it. My validation method needs data from Elasticsearch and hence I have an #Autowired for an elastic client.
When I run mvn clean test, I am getting a NullPointerException stating that this elastic client is null. I think the issue is that this test is devoid of anything to do with Spring and hence the issue, but I do not know how to overcome this.
My test needs to fetch data from Elasticsearch and hence the ValidationProcessor POJO does need #Autowired.
When you extend CamelTestSupport then it's not a Spring application. You need to extend CamelSpringTestSupport. That would create Camel in a Spring runtime, and then allow beans to have IoC via Spring. This kind of testing is often used with Camel XML routes where the routes are defined in XML files. However, you can have a plain XML file and refer to routes in Java DSL as well.
However, as Makoto answers, then vanilla Spring unit testing is of late often about using all those gazillion annotations. Camel has support for that as well as his answer shows. This is also how for example Spring Boot testing is done, etc.
You can find some unit tests in camel-test-spring you can use as inspiration as well.
What I've discovered is that it's unwise to extend CamelTestSupport when you want to use anything with Spring. In fact, there's a better way to do it - use the CamelSpringBootRunner instead.
Well...I say "better". You're going to find yourself attaching a ton of annotations. Of the things you'll need:
A boostrapper to ensure that you're bootstrapping Camel correctly
The routes you wish to add to the classpath (and all of the beans); this ensures that they get added to Camel's registry through Spring's registry
You have to dirty the context after every run or your tests get into a wonky state.
You can automock endpoints by specifying either #MockEndpoints or #MockEndpointsAndSkip. The former sends the data along to the actual route.
Below is just a start. Many of these annotations and their documentation can be found in the formal docs.
#RunWith(CamelSpringBootRunner.class)
#BootstrapWith(SpringBootTestContextBootstrapper.class)
#ActiveProfiles("test")
#SpringBootTest(classes = { YourEntryPointClass.class })
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
#UseAdviceWith
#MockEndpoints("direct:*")
public class RouteTests {
}
I'm trying to use Springockito and spies to verify that calls were made/not made on a service method during an end-to-end test. I'm autowiring the service that the process will also get, and spy on it. Although myService instance is instrumented, verify() does not verify previous calls, but makes a call to the original method and passes a null parameter. Why is this?
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = PatchedSpringockitoContextLoader.class, locations = {
"classpath:/config.xml"
})
...
#Autowired
#WrapWithSpy
private MyService myService;
...
#Before
public void setup() {
initMocks(this);
...
}
...
#Test
public void test() {
// run the process that may or may not call the service
verify(myService, never()).myMethod(any(MyParam.class));
}
What might be happening here is that your spied object uses annotations (e.g #Transactional) that requires Spring to add an AOP proxy around your spy, which causes Mockito to malfunction.
I've had the same issue as yours although I do not use Spock, and I solved it by getting a reference to the proxied mock or spy from the Spring proxy.
Check out the suggested hack in this GitHub issue report.
I am not using Spring Boot, so I wrapped the workaround code in a #BeforeClass method.
Here's a weird one. I've got a few tests failing because an aspect is being applied, so an autowired service is null, bad things ensue. The issue is that I can't understand how the aspect is even being applied, since in the test I construct the object under test with new.
#RunWith(MockitoJUnitRunner.class)
public class TheControllerTest {
#Spy
private TheController controller = new TheController();
#Mock
private HttpServletRequest request;
#Mock
private ConfigService configService;
....
#Before
public void setup() {
controller.setConfigService(configService);
....
}
#Test
public void testGetAccountsList() throws Exception {
Mockito.when(accountService.getAllAccounts()).thenReturn(Arrays.asList(account1, account2));
Map<String, Object> result = controller.getAccountsList(request);
...
}
}
I'm obviously omitting plenty of code, but really, I just don't understand how, given how controller is instantiated, it could have had the advice applied.
One possible reason could be if you are running this in Eclipse - in a project with ajbuilder enabled, even if you are explicitly expecting Spring AOP through dynamic proxies, ajbuilder would actually perform compile team weaving, and hence you would see advice enhanced classes even using normal "new". Can you please check this, the fix would be to disable "ajbuilder" - here is one reference - JUnit weaving wrong Spring AOP Bean