Mockito: Having trouble executing method in class annotated with #MockBean - spring

I'm new to Mockito, so I apologize if there is a simple solution.
I have a test class set up:
#ExtendWith(SpringExtension.class)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class dummyServiceTest{
#InjectMocks ServiceA serviceA;
#MockBean ServiceB serviceB;
#BeforeEach
void before() throws Exception {
openMocks(this);
ReflectionTestUtils.setField(serviceA, "maxRetries", 3);
}
#Test
public void serviceATest() {
...
serviceA.someMethod1();
...
}
...
}
ServiceA class:
#Component
public class ServiceA {
#Autowired ServiceB serviceB;
...
public void someMethod1() {
...
serviceB.someMethod2();
...
}
}
ServiceB class:
public class ServiceB {
...
public void someMethod2() {
...
}
}
TLDR: a method in the ServiceA class is called in the test class. That method calls a method in ServiceB
When running my tests, I noticed that none of the logs in someMethod2 were displayed. I tried running in debugging mode and noticed that when I add a breakpoint in that method, it is never hit. Only when I add a breakpoint to the very first line of the method does it reach that endpoint, but doesn't proceed to the next line in that method. Also, when it reaches the first line of the method, it does not have access to any global variables or even the arguments (note: I didn't show any arguments here but someMethod3() has arguments passed to it). I should mention that I added a dummy method to ServiceB just to see if it was an issue with someMethod2(). I simply added some logging lines to that method, but the same issue arises for it as well.
My question is, why can't someMethod3 be executed? My guess is that it has to do with the fact that ServiceB is annotated with #MockBean, but not sure exactly.
Any help is appreciated.
EDIT: I updated the structure of my code above. I apologize for giving the wrong code.

You need to create a mock for service b, since that's the one being injected into service a. That mock won't call service c's method - that behavior is internal to service b, which isn't of interest in the test for a.

Related

Quarkus encapsulation behavior

I have this class:
#ApplicationScoped
public class BookService {
public boolean doorClosed;
public boolean isDoorClosed() {
return doorClosed;
}
}
And I have this test class:
#QuarkusTest
public class BookServiceTest {
#Inject BookService bookService;
#Test
public void testBookServiceDoor() throws InterruptedException {
bookService.doorClosed=true;
assertTrue(bookService.doorClosed);
assertTrue(bookService.isDoorClosed());
}
}
I am surprised that the last test assertion fails. The first passes but the last fails. It almost seems like that the getter/setters are using different variables than the one I am accessing directly.
I did the same test with Spring Boot and got the two assertions passing:
#Service
public class BookService {
public boolean doorClosed;
public boolean isDoorClosed() {
return doorClosed;
}
}
And the test:
#SpringBootTest
public class BookServiceTest {
#Autowired BookService bookService;
#Test
public void testBookServiceDoor() throws InterruptedException {
bookService.doorClosed=true;
assertTrue(bookService.doorClosed);
assertTrue(bookService.isDoorClosed());
}
}
Using the #ApplicationScoped annotation makes the class a normal scoped bean. This means that the container never injects an actual instance of the class; instead, you get a proxy that will lookup the correct instance on each method invocation.
This is most commonly useful for example with #RequestScoped beans, where you get a single proxy, which will dispatch method invocations to instances that belong to the "current request" (typically an HTTP request). However, there are good reasons why you might want this for application scoped beans as well (e.g. when you want lazy initialization).
The rule is: never access fields directly on normal scoped beans, only call methods.
If you want, you can make the class #Singleton. That is a pseudo scope, and you get no proxy, you get the actual instance that you can work with directly.

Spring Boot - Testing - tearDown for a bean

I use #EmbeddedKafka annotation as follows to have a kafka mock:
#ExtendWith(SpringExtension.class)
#SpringBootTest
#EmbeddedKafka(partitions = 1,
topics = {"topic"},
brokerProperties = {
"auto.create.topics.enable=${topics.autoCreate:false}",
"delete.topic.enable=${topic.delete:true}",
"broker.id=2"})
public class KafkaUsersTest {
#Autowired
private EmbeddedKafkaBroker embeddedKafka;
#Test
public void test1() {
// test something
}
#Test
public void test2() {
// test something
}
...
}
Now, after The tests finish I'd like to close the embeddedKafka bean. Something like this:
#AfterAll
public void tearDown(){
embeddedKafka.getKafkaServers().forEach(KafkaServer::shutdown);
embeddedKafka.getKafkaServers().forEach(KafkaServer::awaitShutdown);
}
The problem is:
An #AfterAll method can only be static.
If I make it static - then also the embeddedKafka has to be static, and then #Autowired annotation will not work.
I guess I can the bean to a static field from one of the tests and then use it in the tearDown(), but it's really ugly.
What is the "good practice" for closing a bean only once after all tests have completed?
An #AfterAll method can only be static.
That's not true.
From the JUnit 5 User Guide:
Denotes that the annotated method should be executed after all #Test, #RepeatedTest, #ParameterizedTest, and #TestFactory methods in the current class; analogous to JUnit 4’s #AfterClass. Such methods are inherited (unless they are hidden or overridden) and must be static (unless the "per-class" test instance lifecycle is used).
An #AfterAll method can be non-static if you use #TestInstance(Lifecycle.PER_CLASS). This is also documented in the JUnit 5 User Guide:
The "per-class" mode has some additional benefits over the default "per-method" mode. Specifically, with the "per-class" mode it becomes possible to declare #BeforeAll and #AfterAll on non-static methods as well as on interface default methods.

Spring Boot - Test for controller fails with 404 code

I want to write a test for controller. Here is test snippet:
#RunWith(SpringRunner.class)
#WebMvcTest(WeatherStationController.class)
#ContextConfiguration(classes = MockConfig.class)
public class WeatherStationControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private IStationRepository stationRepository;
#Test
public void shouldReturnCorrectStation() throws Exception {
mockMvc.perform(get("/stations")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
controller code snippet:
#RestController
#RequestMapping(value = "stations")
public class WeatherStationController {
#Autowired
private WeatherStationService weatherService;
#RequestMapping(method = RequestMethod.GET)
public List<WeatherStation> getAllWeatherStations() {
return weatherService.getAllStations();
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public WeatherStation getWeatherStation(#PathVariable String id) {
return weatherService.getStation(id);
}
MockConfig class:
#Configuration
#ComponentScan(basePackages = "edu.lelyak.repository")
public class MockConfig {
//**************************** MOCK BEANS ******************************
#Bean
#Primary
public WeatherStationService weatherServiceMock() {
WeatherStationService mock = Mockito.mock(WeatherStationService.class);
return mock;
}
Here is error stack trace:
java.lang.AssertionError: Status
Expected :200
Actual :404
I can get what is wrong here.
How to fix test for controller?
HTTP code 404, means no resource found (on the server) for your request, which I think that your controller is not visible(let me say is not scanned) by spring boot.
A simple solution is scanning a parent package in MockConfig class, so spring can pick up all beans,
#ComponentScan(basePackages = "edu.lelyak") // assuming that's the parent package in your project
if you don't like this approach, you can add the controller's package name in basePackages
#ComponentScan(basePackages = {"edu.lelyak.controller","edu.lelyak.repository")
BTW, you don't have to manually set up WeatherStationService in MockConfig class, Spring boot can inject a mock for you and automatically reset it after each test method, you should just declare it in your test class:
#MockBean
private IStationRepository stationRepository;
On the other hand, you should mock weatherService.getAllStations() before calling get("/stations") in your test method (as you're not running integration test), so you can do:
List<WeatherStation> myList = ...;
//Add element(s) to your list
Mockito.when(stationService.getAllStations()).thenReturn(myList);
You can find more in :
Testing improvements in Spring Boot 1.4
Spring Boot features: Testing
I had the same issue. The controller was not getting picked up despite specifying it with #WebMvcTest(MyController.class). This meant all of its mappings were ignored, causing the 404. Adding #Import(MyController.class) resolved the issue, but I didn't expect the import to be necessary when I'm already specifying which controller to test.
I am not sure why your test is not working. But I got another solution which works for me.
#SpringBootTest
public class ControllerTest {
#Autowired
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new TestController()).build();
}
#Test
public void shouldReturnCorrectStation() throws Exception {
mockMvc.perform(get("/stations")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
After some debugging, it appears that the target controller is simply not registered as a method handler. Spring scans the beans for the presence of RestController annotation.
But the problem is that the annotation could be found only if the bean is proxied via CGLIB, but for the case when we use WebMvcTest it's proxied by JDK.
As a result, I searched for the configuration which is responsible for making the choice, and the one was finally found AopAutoConfiguration. So when SpringBootTest is used this one is autoloaded when you need WebMvcTest+PreAuthorize in your controllers, then simply use:
#Import(AopAutoConfiguration.class)
I import external configuration class by #ContextConfiguration(classes = MyConfig.class)
When I changed in MyConfig annotation #Configuration into #TestConfiguration it started to work properly.
I couldn't find a good answer but I could find one of the causes.
I was using in my tests the #PreAuthorize on the RestController.
You can mock the Oauth with this tip on the integration tests that use SpringBootTest. For SpringBootTest, this works very well too, but using SpringBootTest you load a lot of other resources (like JPA) that is not necessary to do a simple Controller test.
But with #WebMvcTest this not works as expected. The use of the WithMockOAuth2Scope annotation can be enough to stop the 401 error from authentication problem, but after that the WebMvcTest can't find the rest endpoint, returning the 404 error code.
After removing the #PreAuthorize on Controller, the test with WebMvcTest pass.
Based on the accepted answer, in my case I had copied and modified the file based on another test, but forgot to change the name for the controller on the top of the class, that being the reason why it was not finding the resource, as the error says.
#RunWith(SpringRunner.class)
#WebMvcTest(AnswerCommandController.class)
public class AnswerCommandControllerTest {
Here is a different approach to the controller test that worked for me.
Assumption: The class WeatherStationService is a #SpringBootApplication
Then, the test class below should work for you:
#RunWith(SpringRunner.class)
#SpringApplicationConfiguration(WeatherStationService.class)
#WebIntegrationTest
public class WeatherStationControllerTest {
#Autowired
private WebApplicationContext context;
MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
#Test
public void shouldReturnCorrectStation() throws Exception {
mockMvc.perform(get("/stations")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk();
}
}
With this test setup, you should no longer need the MockConfig class.
In my case it was about a missing starting slash /
I've appended / to both RequestMapping value and MockHttpServletRequestBuilder post urlTemplate parameters as first character.
In case anyone is wondering.
If we don't use #ContextConfiguration, #WebMvcTest annotation will load the REST controller class. Otherwise, when we use use #ContextConfiguration, seems ContextConfiguration clear the context REST controller config. We need to add the REST controller to ContextConfiguration such as:
#ContextConfiguration(classes = {MockConfig.class, WeatherStationController.class})

Unit Testing of Spring MVC Controller: Failing to fully perform request

I have a simple controller defined as below:
#Controller
#RequestMapping("/rest/tests")
public class TestController {
#Autowired
private ITestService testService;
#RequestMapping(value="/{id}", method=RequestMethod.DELETE)
#ResponseStatus(value = HttpStatus.OK)
public void delete(#PathVariable Integer id)
{
Test test = testService.getById(id);
testService.delete(test);
}
}
I have been trying to test the delete method, and have not succeeded so far. The test I have written is pretty simple too.
public class MockmvcTest {
#InjectMocks
private TestController test;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(test).build();
}
#Test
public void myTest() throws Exception {
this.mockMvc.perform(delete("/rest/tests/{id}", new Integer(4)))
.andExpect(status().isOk()
}
}
I have tested the method using "advanced rest client" extension in chrome and it works as expected.
By working I mean that the entity with the given id is deleted from database.
When myTest() is executed the status code is still 200, but the entity is not removed from the database.
What might be the reason behind this behavior?
You're using Mockito to inject mock service beans into your TestController (in particular, ITestService). All mocks by definition have no behaviour until you specify it, by default all operations you perform will either do nothing or return null. You can easily confirm that by setting a breakpoint inside TestController.delete method, executing the test in debug mode and inspecting values of test and testService variables.
Mockito is used for unit-level tests that replace SUT's collaborators with a mock that you set up to behave in a certain verifiable way. Once you call a method on your SUT (in your case that's TestController) you can assert whether it adheres to its contract or not.
It's actually a big no-no to allow your automated tests to modify a real instance of a database.

How to call Springs service method from controller (Junit)

I have seen example , how to call spring controller using mockito.
Using Mock I call Spring MVC controller.
Controller Invokes Spring service class.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
public class TestController {
#Mock
private TestService testService;
#InjectMocks
private PaymentTransactionController paymentController;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.setMockMvc(MockMvcBuilders.standaloneSetup(paymentController).build());
}
#Test
public void test() throws Exception {
this.mockMvc.perform(post("/tr/test").content(...)).andExpect(status().isOk());
// testService.save(); <-- another way
}
Ok it works well. I calls my Spring controller very well. But In Spring controller I have Injected Service Layer.
#Autowired
private TestService serviceTest;
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody()
public String test(HttpServletRequest request) {
...
serviceTest.save();
// in save method I call dao and dao perist data;
// I have injected dao intrface in serviceTest layer
...
return result;
}
The problem is that, my app does not invokes save method, it is not entered in it. I have no error too. The same result is when I call save() method from Junit (I have commented it in test() method).
When I debug, I have seen that interrupt method happens of org.mockito.internal.creation.MethodInterceptorFilter
How to solve this problem? what happens?
If you are doing a unit test of your controller, you should mock the service layer (what you are doing). In this kind of test, you just control that :
the correct methods of the controller are triggered and they produce what is expected
the correct methods in service layer are called ... in the mock
You simply have to configure the return values of the methods of the mock (if relevant), or control what was called
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.setMockMvc(MockMvcBuilders.standaloneSetup(paymentController).build());
// set return values from the mocked service
when(testService.find(1)).thenReturn(...);
}
and verify later what has been called
#Test
public void test() throws Exception {
this.mockMvc.perform(post("/tr/test").content(...)).andExpect(status().isOk());
// testService.save(); <-- another way
verify(testService, times(1)).save();
}
If you want to do an integration test, you do not mock the service, but setup an application context to inject real beans, but ordinarily use an embedded database instead of the real one.
just change #InjectMocks to #Autowired. This fix the issue! In this case you are not mocking, you are invoking method with real data.
As I understand, you perform post to "/tr/test" resource, but request mapping in your controller is '/payment'. Make sure you post to resource mapped in the controller.

Resources