If there compatibility between RestAssured(with MockMvc) and MockK - spring

I am wanting to create/run an Integration test to assess the processes from the controller all the way to the services, as well as all the business logic in-between. However, I don't want to make an actual request that leaves the application to another microService as it is not part of the test.
I am newer to Kotlin, In Java, I know that I could pull in Mockito and call #SpyBean on the restTemplate and then I could use doReturn(restTemplate.getForObject(...) to make sure that the REST request is mocked. I could even call verify() and make sure it ran the number of times expected. Is there a way to accomplish this using kotlin, MockK, RestAssured, and MockMVC?
note: I did pull in io.rest-assured:kotlin-extensions but I didn't see the need of using it since the test runs perfectly when I don't use #SpyK
#ExtendWith(SpringExtension::class)
#SpringBootTest
#AutoConfigureMockMvc
internal class FooControllerIT() {
#Autowired
lateinit var mockMvc: MockMvc
#SpyK
#Qualifier("restTemplateTwo")
val restTemplate: RestTemplate = mockk()
val foo= Foo(Name("Test", "Testerson"))
#BeforeEach
fun setUp() = MockKAnnotations.init(this)
#Test
fun getFooTest() {
every { restTemplate.getForObject("https://www.test.com/some-endpoint", Foo::class.java) } returns Foo()
given()
.mockMvc(mockMvc)
.auth().with(csrf())
.`when`()
.get("/rest/${foo.name.first + foo.name.last}")
.then()
.log()
.all()
.statusCode(200)
// .body("", equalTo(""))
}
}

Related

Kotlin, Spring book, Mockito, #InjectMocks, Using different mocks than the ones created

I am trying to test a class like
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#SpringBootTest(classes = [TestConfig::class])
#ExtendWith(MockitoExtension::class)
class TotalCalculatorTests {
#Mock
lateinit var jdbcRepo: JDBCRepo
#Mock
lateinit var userCache : LoadingCache<ApproverLevel, List<Approver>>
#InjectMocks
lateinit var calculator: TotalCalculator
#Test
fun totalOrder() {
// val calculator = TotalCalculator(userCache,jdbcRepo)
calculator.total(ItemA(),ItemB())
verify(jdbcRepo).getTotal()
}
}
I get an error Actually, there were zero interactions with this mock. but if I uncomment the // val calculator = TotalCalculator(userCache,jdbcRepo) it works. I assumed mockito would be using the mocks from the test class but that appears to not be true. How can I get the instance of JDBCRepo being used by the #InjectMocks?
It looks like you would like to run the full-fledged spring boot test with all the beans but in the application context you would like to "mock" some real beans and provide your own (mock-y) implementation.
If so, the usage of #Mock is wrong here. #Mock has nothing to do with spring, its a purely mockito's thing. It indeed can create a mock for you but it won't "substitute" the real implemenation with this mock implementation in the spring boot's application context.
For that purpose use #MockBean annotation instead. This is something from the spring "universe" that indeed creates a mockito driven mock under the hood, but substitutes the regular bean in the application context (or even just adds this mock implementation to the application context if the real bean doesn't even exist).
Another thing to consider is how do you get the TotalCalculator bean (although you don't directly ask this in the question).
The TotalCalculator by itself is probably a spring been that spring boot creates for you, so if you want to run a "full fledged" test you should take the instance of this bean from the application context, rather than creating an instance by yourself. Use annotation #Autowired for that purpose:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#SpringBootTest(classes = [TestConfig::class])
#ExtendWith(MockitoExtension::class) // you don't need this
class TotalCalculatorTests {
#MockBean
lateinit var jdbcRepo: JDBCRepo
#MockBean
lateinit var userCache : LoadingCache<ApproverLevel, List<Approver>>
#Autowired // injected by the spring test infrastructure
lateinit var calculator: TotalCalculator
#Test
fun totalOrder() {
// val calculator = TotalCalculator(userCache,jdbcRepo)
calculator.total(ItemA(),ItemB())
verify(jdbcRepo).getTotal()
}
}
Now, all this is correct if your purpose to indeed running the full microservice of spring boot and make an "integration" testing
If alternatively you just want to check the code of your calculator, this might be an overkill, you can end up using mockito and plain old unit testing. In this case however you don't need to even start the spring (read create an application context) during the test, and of course there is no need to use #SpringBootTest / #MockBean/#Autowired.
So it pretty much depends on what kind of test is really required here

Springboot test controller via WebClient instead of WebTestClient

I'm attempting something perhaps misguided but please help.
I would like to test springboot controller via #WebFluxTest. However I would like to use WebClient instead of WebTestClient. How can this be done?
So far, I managed to use reflection to get ExchangeFunction out of WebTestClient and assign it to WebClient - and it works! Calls are made, controller responds - wonderful. However I don't think this is good approach. Is there a better way?
Thank you.
Ideally, you should use a WebTestClient which is more of a convenience wrapper around the WebClient. Just like the TestRestTemplate is for a RestTemplate. Both allow a request to be created and you can easily make assertions. They exist to make your test life easier.
If you really want to use a WebClient instead of a WebTestClient and do the assertions manually (which means you are probably complicating things) you can use the WebClient.Builder to create one. Spring Boot will automatically configure one and you can simply autowire it in your test and call the build method.
#SpringBootTest
public void YourTest {
#Autowired
private WebClient.Builder webClientBuilder;
#Test
public void doTest() {
WebClient webClient = webClientBuilder.build();
}
}
The same should work with #WebFluxTest as well.
Ok, after much experimentation here is a solution to test springboot controller & filters via a mocked connection - no webservice, no ports and quick test.
Unfortunately I didn't work out how to do it via #WebFluxTest and WebClient, instead MockMvc can be used to achieve desired result:
#ExtendWith(SpringExtension.class)
#Import({SomeDependencyService.class, SomeFilter.class})
#WebMvcTest(controllers = SomeController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class)
#AutoConfigureMockMvc()
public class SomeControllerTest {
#MockBean
private SomeDependencyService someDependencyService;
#Autowired
private MockMvc mockMvc;
private SomeCustomizedClient subject;
#BeforeEach
public void setUp() {
subject = buildClient();
WebClient webClient = mockClientConnection();
subject.setWebClient(webClient);
}
private WebClient mockClientConnection() {
MockMvcHttpConnector mockMvcHttpConnector = new MockMvcHttpConnector(mockMvc);
WebClient webClient = WebClient.builder().clientConnector(mockMvcHttpConnector).build();
return webClient;
}
#Test
public void sample() {
when(SomeDependencyService.somePersistentOperation(any(), any())).thenReturn(new someDummyData());
SomeDeserializedObject actual = subject.someCallToControllerEndpoint("example param");
assertThat(actual.getData).isEquals("expected data");
}
}
Now it is possible to test your customized client (for example if you have internal java client that contains few important customization like security, etags, logging, de-serialization and uniform error handling) and associated controller (and filters if you #import them along) at the cost of a unit test.
You do NOT have to bring up entire service to verify the client and controller is working correctly.

Using MockRestServiceServer only in subset of tests

I want to test outgoing HTTP calls from my service using MockRestServiceServer. I got it working using following code:
#SpringBootTest
class CommentFetcherTest {
#Autowired
RestTemplate restTemplate;
#Autowired
CommentFetcher commentFetcher;
#Test
public void shouldCallExternalService(){
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(ExpectedCount.once(), requestTo("/test/endpoint")).andExpect(method(HttpMethod.GET));
//when
commentFetcher.fetchData();
mockServer.verify();
}
}
However the problem I run into is that RestTemplate is a bean shared between all tests from suite what makes impossible to further run integration tests which should be calling external services. This results in :
java.lang.AssertionError: No further requests expected
How is it possible to use MockRestServiceServer only for subset of tests?
I can't get rid of Dependency Injection through #Autowired and construct my service in tests such as
RestTemplate restTemplate = new RestTemplate();
CommentFetcher commentFetcher = new CommentFetcher(restTemplate);
As this would not read properties from application.properties necessary to proper functioning of my service.
Spring Boot provides a convenient way to test your client classes that make use of Spring's RestTemplate using #RestClientTest.
With this annotation, you get a Spring Test Context that only contains relevant beans and not your whole application context (which is what you currently get with #SpringBootTest).
The MockRestServiceServer is also part of this context and the RestTemplate is configured to target it.
A basic test setup can look like the following:
#RestClientTest(CommentFetcher.class)
class UserClientTest {
#Autowired
private CommentFetcher userClient;
#Autowired
private MockRestServiceServer mockRestServiceServer;
// ...
}
What's left is to stub the HTTP method call and then invoke the method on your class under test (commentFetcher.fetchData(); in your case).
Refer to this article if you want to understand more about how #RestClientTest can be used to test your RestTemplate usage.

Testing classes that make use of Spring 5 WebClient

We're slowly migrating some projects from using the legacy RestTemplate class to the new Spring 5 WebClient. As part of this, we have some existing test classes that make use of Mockito to verify that a given method will reach out to make a GET/POST/whatever to endpoint X using the template.
Given the fluent interface of the WebClient, the same mocking approach isn't really practical. I have spent some time using WireMock, which is great, but unfortunately there seems to be a bug where occasionally the WireMock tests will overrun or hang, and as such I'm considering alternatives.
Does anyone have any other suggestions for frameworks or techniques to use to verify that Spring's WebClient is making expected calls as part of SUT execution?
Spring actually uses OkHttp MockWebServer for testing of the WebClient.
Spring's Example Integration Tests
You can set up ordered mock responses or map mock responses to request details.
Approach 1 (preferred)
MockWebServer sounds like a cool approach(i.e Use WebClient for real, but mock the service it calls by using MockWebServer (okhttp)). An adapted example is:
#ExtendWith(MockitoExtension.class)
class serviceImplTest {
private ServiceImpl serviceImpl;
public static MockWebServer mockWebServer;
#BeforeAll
static void setUp() throws IOException {
mockWebServer = new MockWebServer();
mockWebServer.start();
}
#AfterAll
static void tearDown() throws IOException {
mockWebServer.shutdown();
}
#BeforeEach
void init() {
String baseUrl = String.format("http://localhost:%s", mockWebServer.getPort());
serviceImpl = new ServiceImpl(WebClient.builder(), baseUrl);
}
#Test
#DisplayName("whatever")
void methodName() {
mockWebServer.enqueue(new MockResponse().addHeader("header", "abcde123")); //MockWebServer will respond with the queued stub.
String header = serviceImpl.fetchHeader();
assertThat(header).isEqualTo("abcde123");
}
}
Approach 2
I have also tried the other approach, i.e Use Mockito to mimic the behavior of WebClient, and it's working fine. The only downside is that there is a lot of given()-willReturn(), to cater for each call in the chain(of the fluent WebClient API).

How to dynamically load different class for JUnit tests?

I've written a couple of JUnit tests to test my REST functionality. Since I only want to test REST (and not the database, domain logic, ..) I made a stub filles with dummy data which stands for the rest of the backend.
[EDIT] For example I want to test /customers/all
A GET request will be responded to with an arraylist containing all names.
I therefore use MockMV.
this.mockMvc.perform(get("/customers/all").accept("application/json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isNotEmpty())
.andExpect(jsonPath("$[0].name", is("John")));
When you normally perform a GET request towards /customers/all a request will be sent to the database. Now, to test my REST controller I made a stub which responds to GET /customers/all with a simple arraylist containing just my name (as you can see in the test). When I test this local I simply replace the real class with this stub. How is this done dynamically?
I don't think your approach is the good one. Just use your real controller, but stub its dependencies (using Mockito, for example), just like you would do for a traditional unit test.
Once you have an instance of the controller using stubbed dependencies, you can use a standalone setup and use MockMvc to test, in addition to the controller code, the mapping annotations, the JSON marshalling, etc.
Thias approach is described in the documentation.
Example using Mockito, assuming the controller delegates to a CustomerService:
public class CustomerControllerTest {
#Mock
private CustomerService mockCustomerService;
#InjectMocks
private CustomerController controller;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void shouldListCustomers() {
when(mockCustomerService.list()).thenReturn(
Arrays.asList(new Customer("John"),
new Customer("Alice")));
this.mockMvc.perform(get("/customers").accept("application/json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isNotEmpty())
.andExpect(jsonPath("$[0].name", is("John")));
}
}

Resources