I'm trying to test a Jersey REST service endpoint with RestAssured while mocking its dependency. I use Spring to autowire the endpoint, and I set a mock dependency before executing the test. However, when RestAssured calls the service, it gets re-autowired again, and my mock is replaced with the real implementation. How do I prevent that?
I'm using Spring Boot, Jersey, EasyMock and RestAssured.
My endpoint class:
#Component
#Path("foo")
#Produces(MediaType.TEXT_PLAIN)
public class FooEndpoint {
private FooService service;
#GET
#Produces({MediaType.TEXT_PLAIN})
public String get() {
return service.getFoo();
}
#Autowired
public void setService(FooService service) {
System.out.println(String.format("<<< Endpoint %s. Setting service %s", this, service));
this.service = service;
}
}
FooService:
#Service
public class FooService {
public String getFoo() {
return "real foo";
}
}
Test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = {Application.class}, webEnvironment = WebEnvironment.RANDOM_PORT)
public class FooEndpointIT {
#LocalServerPort
private int port;
#Autowired
private FooEndpoint target;
private FooService serviceMock;
#Test
public void testGet() {
RestAssured.port = port;
String expected = "mock foo";
serviceMock = EasyMock.strictMock(FooService.class);
target.setService(serviceMock);
EasyMock.expect(serviceMock.getFoo()).andReturn(expected);
EasyMock.replay(serviceMock);
String actual = RestAssured.get("foo").body().asString();
System.out.println(actual);
assertEquals("mock foo", actual); // Fails: expected:<[mock] foo> but was:<[real] foo>
}
}
And in the logs I see this:
<<< Endpoint com.foo.foo.FooEndpoint#6972c30a. Setting service com.foo.foo.FooService#57a48985
. . . Tomcat initializes and starts . . .
<<< Endpoint com.foo.foo.FooEndpoint#6972c30a. Setting service EasyMock for class com.foo.foo.FooService
<<< Endpoint com.foo.foo.FooEndpoint#6972c30a. Setting service com.foo.foo.FooService#57a48985
real foo
How do I prevent the FooEndpoint from being re-autowired after I set my mock?
Related
Is it possible to write unit test using Junit 5 mockito for retryable annotations?
I am having a service interface which has only one method, which downloads the file from remote url
#service
interface downloadpdf{
#Retryable(value = { FileNotFoundException.class, HttpClientErrorException.class }, maxAttempts = 5, backoff = #Backoff(delay = 1000))
public string downloadpdffile(string remoteurl, string pdfname);
}
I have tried referring sites and found using Spring4JunitRunner implementation to test retry. Got confused with implementation. Is it possible to write unit test using Junit 5 mockito for retryable annotations?. Could you please elaborate on the solution here?
You need to use #SpringJUnitConfig (which is the equivalent of the JUnit4 runner). Or #SpringBootTest as you are using Boot.
#Retryable only works with beans managed by Spring - it wraps the bean in a proxy.
#SpringBootApplication
#EnableRetry
public class So71849077Application {
public static void main(String[] args) {
SpringApplication.run(So71849077Application.class, args);
}
}
#Component
class RetryableClass {
private SomeService service;
void setService(SomeService service) {
this.service = service;
}
#Retryable
void retryableMethod(String in) {
service.callme();
throw new RuntimeException();
}
#Recover
void recover(Exception ex, String in) {
service.failed();
}
}
interface SomeService {
void callme();
void failed();
}
#SpringBootTest
class So71849077ApplicationTests {
#MockBean
SomeService service;
#Test
void testRetry(#Autowired RetryableClass retryable) {
SomeService service = mock(SomeService.class);
retryable.setService(service);
retryable.retryableMethod("foo");
verify(service, times(3)).callme();
verify(service).failed();
}
}
I was also trying to implement this using Junit5.
Tried various options but that didn't help. Then after googling for few hours, got the following link and it helped to succeed.
https://doctorjw.wordpress.com/2022/04/29/spring-testing-a-single-bean-in-junit-5-springextension/
Reference code below, for detailed explanation, please refer the blog.
#Component
public class MyClass {
private ObjectMapper objectMapper;
private RestTemplate restTemplate;
#Value("${testValue:5}")
private int value;
#Retryable(....)
public void doStuff() throws SomeException {
...
}
}
What I’ve discovered is, if I declare my test class this way:
#ExtendWith( SpringExtension.class )
#Import( { MyClass.class, ObjectMapper.class } )
#EnableRetry
public class MyClassTest {
#Autowired
private MyClass myClass;
#MockBean
private RestTemplate restTemplate;
#Autowired
private ObjectMapper objectMapper;
#BeforeEach
public void setup() {
// If we are going to jack with the object configuration,
// we need to do so on the actual object, not the Spring proxy.
// So, use AopTestUtils to get around the proxy to the actual obj.
TestingUtils.setFieldValue( AopTestUtils.getTargetObject( myClass ), "value", 10 );
}
}
You will notice the inclusion of 1 other class, TestingUtils.class. This class looks like:
public class TestingUtils {
public static void setFieldValue( Object object, String fieldName, Object value ) {
Field field = ReflectionUtils.findField( object.getClass(), fieldName );
ReflectionUtils.makeAccessible( field );
ReflectionUtils.setField( field, object, value );
}
}
All credits goes to the author of the blog.
I'm making test code in spring boot.
But, my test code doesn't save the data using #Before method.
If i request to '/v1/stay/, it return empty array...
Please can you explain what is wrong with my code?
Here is my test code.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class StayControllerTest {
#MockBean
private StayService stayService;
#Autowired
private MockMvc mockMvc;
// givenStay method is the method generating dummy data
#Before
public void before() {
stayService.save(givenStay1());
stayService.save(givenStay2());
stayService.save(givenStay3());
stayService.save(givenStay4());
stayService.save(givenStay5());
}
#Test
#Transactional
void showStayList() throws Exception {
List<StayReq> original = new ArrayList<>();
original.add(givenStay1());
original.add(givenStay2());
original.add(givenStay3());
original.add(givenStay4());
original.add(givenStay5());
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/v1/stay")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andReturn();
System.out.println(result.getResponse());
}
}
And below code blocks are my StayController and StayService
#RestController
#ApiV1
#RequiredArgsConstructor
public class StayController {
private final StayService stayService;
private final ApiService apiService;
#GetMapping("/stay")
public ResponseEntity<Response> stayList() {
return apiService.okResponse(stayService.getList());
}
}
#Service
#RequiredArgsConstructor
public class StayService {
private final StayRepository stayRepository;
private final RoomRepository roomRepository;
public List<StayRes> getList() {
return stayRepository.findAll().stream().map(StayRes::new).collect(Collectors.toList());
}
#Transactional
public void save(StayReq stayReq) {
stayRepository.save(stayReq.toEntity());
}
}
You injected a mock, not a 'real' service. If you want to use a 'real' service - you need to replace #MockBean annotation with #Autowired annotation.
Or alternatively - you can configure mock in the test method to return some predefined data.
My FristService is internally calling a SecondService through feign client, and I am trying to write a test for FirstService and want to mock the call to the second service.
It seems like wiremock is unable to intercept and respond with the mock results but throws the following exception (as it's not mocking)
java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: second-service
at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:90)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:119)
Here is the Test code
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(initializers = {WireMockInitializer.class})
#AutoConfigureMockMvc
public class TestFirstController {
#Autowired
private WireMockServer wireMockServer;
#Inject
public MockMvc mockMvc;
#LocalServerPort
private Integer port;
#AfterEach
public void afterEach() {
this.wireMockServer.resetAll();
}
#Test
public void testSomeMethod() throws Exception {
this.wireMockServer.stubFor(
WireMock.get(urlPathMatching("/second-controller/1"))
.willReturn(aResponse()
.withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.withBody("[{\"id\":1,\"message\":\"Child title 1\"}]"))
);
mockMvc.perform(MockMvcRequestBuilders.request(HttpMethod.GET, "/first-controller/1"))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk());
}
}
here is my business method that first fetches record from the database and then makes a call to the second service
#Override
public First getFirst(Integer id) {
First first = map(respository.findById(id));
//feign-client call
List<Second> seconds = client.getSeconds(id);
first.setChild(seconds);
return first;
}
and here is my feign client and I am not changing anything about it in test mode
#FeignClient("service-2")
public interface SecondApi {
#GetMapping({"/second-controller/{id}"})
SomeData getData(#PathVariable("id") Integer id);
}
I hava a controller like this:
#PostMapping
public ResponseEntity<RestResponse> test(#RequestBody #Valid RestRequest request) {
BodyRs BodyRs = service.setup(request.getBody());
return ResponseEntity.status(HttpStatus.OK).body(RestResponse.builder().body(BodyRs).build());
}
When I call this controller with Request = '{}'(RestRequest not blank), it jump into:
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#Autowired
HolderService holderService;
#Autowired
LogService logService;
#Override
protected ResponseEntity<Object> handleNotValid(WebRequest request) {
RestResponseHeader responseHeader = RestResponseHeader.builder()
.respCode("10")
.respDesc(fieldError.getField() + " " + fieldError.getDefaultMessage())
.build();
RestResponse restResponse = RestResponse.builder()
.header(responseHeader)
.build();
holderService.setRestResponse(restResponse);
logService.log("100");
return ResponseEntity.ok().body(restResponse);
}
How can I Mock LogService, because when I run unit test without starting MQ, it error at this line 'logService.log("100")'.
-I used to Mock it outside but when code jump into RestExceptionHandler, it is not a Mock object anymore.
If you are talking about unit test for RestExceptionHandler, you can either inject the mock, or (preferably in my mind) switch to use constructor autowiring and then it is easier to pass a mock in the constructor:
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
private HolderService holderService;
private LogService logService;
#Autowired
public RestExceptionHandler(HolderService holderService, LogService logService) {
this.holderService = holderService;
this.logService = logService;
}
...
}
If you are talking about integration test on your controller, you can use Springboot's MockBean annotation to create a mocked bean of your logger service
I am writing a simple REST client, my service class has one method using
RestTemplate.getForObject()
I would like to practice testing but I don't really know if we should test the class. I also do not know how to test method that does not do much. Should I write any unit tests?
You can test, that request has been sent. For example you have the next service:
#Service
public void QueryService {
private final RestTemplate restTemplate;
#Autowired
public QueryService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public List<Employee> void makeQuery() {
ResponseEntity<List<Employee>> response = restTemplate.getForObject(
"http://localhost:8080/employees",
EmployeeList.class);
return response.getEmployees();
}
}
For the method makeQuery you can write the next unit test:
#RunWith(MockitoJUnitRunner.class)
public class QueryServiceTest {
#Mock
private RestTemplate restTemplate;
#InjectMocks
private QueryService queryService;
#Test
public void testMakeQueryRequestHasBeenSent() {
List<Employee> result = queryService.makeQuery();
// This string checks that method getForObject has been invoked
verify(restTemplate).getForObject(anyString(), any(Class.class));
}
}
In the future, if you will change makeQuery method and forget to send request using restTemplate, this test will fail.