unable to mock a constructor with Mockito - java

Using Mockito version 4.8.0
The controller method I need to test
#GetMapping(value = "getStringBuiltByComplexProcess")
public String getStringBuiltByComplexProcess(#RequestParam String firstName, #RequestParam String lastName ) {
Author a = new Author();
return a.methodWhichMakesNetworkAndDatabaseCalls(firstName, lastName);
}
here is the test method
#Test
public void testGetStringBuiltByComplexProcess01() {
final String firstName = "firstName";
final String lastName = "lastName";
try (MockedConstruction<Author> mock = mockConstruction(Author.class)) {
Author authorMock = new Author();
when(authorMock.methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName))).thenReturn("when worked");
assertEquals("when worked", ut.getStringBuiltByComplexProcess(firstName, lastName), "Strings should match");
verify(authorMock).methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName));
}
}
fails with a message of
org.opentest4j.AssertionFailedError: strings should match ==> expected: <when worked> but was: <null>
In this simplified example the controller method has more code but the core of what is not working is mocking the object which the controller method constructs.

The object you create on line
Author authorMock = new Author();
is different than the one created in the getBooksByAuthor() function. A debugger should show you that.
You can use mock.constructed().get(0) to get the object created in getBooksByAuthor(), but by the time you can do this, getBooksByAuthor() has already finished and you can't do much with that mock.
It's not exactly clear what your objective is. I guess you want to check that the Author object is created in a certain way, and the lines involving getFullName() aren't part of the actual code, just something you added to experiment, because they don't do anything.
If you want to verity that the object passed to dataAccessService satisfies some conditions, what you need is an ArgumentCaptor. Something like
ArgumentCaptor<Author> authorCaptor = ArgumentCaptor.forClass(Author.class);
when(dataAccessServiceMock.getBooks(authorCaptor.capture())).thenReturn(books);
List<Book> result = ut.getBooksByAuthor(firstName, lastName);
Author author = authorCaptor.value();
assertEquals(firstName, author.getFirstName());

If you use MockInitializer for stubbing , it should solve your problem :
try (MockedConstruction<Author> mocked = mockConstruction(Author.class, (mock, context) -> {
when(authorMock.methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName))).thenReturn("when worked");
})) {
assertEquals("when worked", ut.getStringBuiltByComplexProcess(firstName, lastName), "Strings should match");
verify(authorMock).methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName));
}
}
But a much better way to test the controller is to use MockMvc. It allows you to test for a given HTTP request , do you configure spring-mvc properly such that it can do the following things correctly :
if the HTTP request can be parsed properly to execute the expected controller method with the expected paramater
if it can deserialize the object returned from the controller method into a expected JSON structure.
it allows to configure the current user who make the HTTP call and verify if he has enough permission to call this API and if not, will it return the expected error response
etc.
All of these things cannot be tested by your fragile mocking constructor approach.
For more details about MockMvc, refer to this guide.

Related

Is it possible to add an assertion message when using MockMvc?

Most of the times instead of adding comments in an ordinary JUnit assertion, we add a message to the assertion, to explain why this is assertion is where it is:
Person p1 = new Person("Bob");
Person p2 = new Person("Bob");
assertEquals(p1, p2, "Persons with the same name should be equal.");
Now, when it comes to end point testing in a Spring Boot web environment I end up with this:
// Bad request because body not posted
mockMvc.perform(post("/postregistration")).andExpect(status().isBadRequest());
// Body posted, it should return OK
mockMvc.perform(post("/postregistration").content(toJson(registrationDto))
.andExpect(status().isOk()));
Is there a way to get rid of the comments and add a message to this kind of assertion? So, when the test fails I will see the message.
You can provide a custom ResultMatcher:
mockMvc.perform(post("/postregistration")
.content(toJson(registrationDto))
.andExpect(result -> assertEquals("Body posted, it should return OK", HttpStatus.OK.value() , result.getResponse().getStatus())))
mockMvc.perform(post("/postregistration"))
.andExpect(result -> assertEquals("Bad request because body not posted", HttpStatus.BAD_REQUEST.value(), result.getResponse().getStatus()));
Explaination:
As of today the method .andExpect() accepts only one ResultMatcher. When you use .andExpect(status().isOk()) the class StatusResultMatchers will create a ResultMatcher in this way:
public class StatusResultMatchers {
//...
public ResultMatcher isOk() {
return matcher(HttpStatus.OK);
}
//...
private ResultMatcher matcher(HttpStatus status) {
return result -> assertEquals("Status", status.value(), result.getResponse().getStatus());
}
}
As you can see the message is hard-coded to "Status" and there is no other built in method to configure it. So even though providing a custom ResultMatcher is a bit verbose, at the moment might be the only feasible way using mockMvc.
I figured out that assertDoesNotThrow responds hence improves the situation (according to what I ask):
assertDoesNotThrow(() -> {
mockMvc.perform(post("/postregistration")).andExpect(status().isBadRequest());
}, "Bad Request expected since body not posted.");

Passing json object to an endpoint developed with spring

I have an endpoint I created using spring.io. My GetMapping declaration can be seen below
#ApiOperation(
value = "Returns a pageable list of CustomerInvoiceProducts for an array of CustomerInvoices.",
notes = "Must be authenticated.")
#EmptyNotFound
#GetMapping({
"customers/{customerId}/getProductsForInvoices/{invoiceIds}"
})
public Page<CustomerInvoiceProduct> getProductsForInvoices(
#PathVariable(required = false) Long customerId,
#PathVariable String[] invoiceIds,
Pageable pageInfo) {
//Do something fun here
for (string i: invoiceIds){
//invoiceIds is always empty
}
}
Here is how I am calling the url from postman and passing the data.
http://localhost:8030/api/v1/customers/4499/getProductsForInvoices/invoiceIds/
{
"invoiceIds": [
"123456",
"234566",
"343939"
]
}
My string array for invoiceIds is always empty in the for loop Nothing gets passed to the array. What am I doing wrong?
The mapping you are using is this:
customers/{customerId}/getProductsForInvoices/{invoiceIds}
Both customerId and invoiceIds are Path variables here.
http://localhost:8030/api/v1/customers/4499/getProductsForInvoices/invoiceIds/
The call you are making contains customerId but no invoiceIds. Either you can pass the list in place of invoiceIds as String and read it as a String and then create a List by breaking up the List - which will be a bad practice.
Other way is to change your path variable - invoiceId to RequestBody.
Generally, Path Variables are used for single id or say navigating through some structured data. When you want to deal in a group of ids, the recommended practice would be to pass them as RequestBody in a Post method call rather than a Get method call.
Sample code snippet for REST API (post calls):
Here, say you are trying to pass Employee object to the POST call, the REST API will look like something below
#PostMapping("/employees")
Employee newEmployee(#RequestBody Employee newEmployee) {
//.. perform some operation on newEmployee
}
This link will give you a better understanding of using RequestBody and PathVariables -
https://javarevisited.blogspot.com/2017/10/differences-between-requestparam-and-pathvariable-annotations-spring-mvc.html
https://spring.io/guides/tutorials/rest/

How to Create a JUnit Test of a List<Overview>

I am currently stuck trying to create a unit test for this piece of code I have. I honestly can't figure out at all how to create a unit test for these lines of code. I have looked multiple places online and couldn't find anything. Its probably just because I don't understand unit test so I can't figure out how to create this one but could someone help me please?
public List<Overview> findOverviewByStatus(String status) throws CustomMongoException {
List<Overview> scenarioList = new ArrayList<Overview>();
LOGGER.info("Getting Scenario Summary Data for - {}", status);
Query query = new Query(Criteria.where("status").is(status));
if (mongoTemplate == null)
throw new CustomMongoException("Connection issue - Try again in a few minutes",
HttpStatus.FAILED_DEPENDENCY);
LOGGER.info("Running Query - {}", query);
scenarioList = mongoTemplate.find(query.with(new Sort(Sort.Direction.DESC, "lastUpdatedDate")), Overview.class);
return scenarioList;
}
So you want to unit test the method. Start with pretending you don't know what the code looks like (black box testing).
What happens if you call it with status of null, and then status of empty string?
What are some status string that return expected values?
Add all these as asserts to your test method to make sure that if someone changes this method in the future the unit test makes sure that it returns the expected result.
That is all a unit test usually does, makes sure that the code behaves in a predictable way and safeguard against change that violates a contract you created for the method when you wrote it.
For example:
import org.junit.Assert;
import org.junit.Test;
public class MyObjectTest {
#Test
public void testMyObjectMethod() {
// Create the object that contains your method (not in the sample you provided)
MyObjectToTest obj = new MyObjectToTest();
// Check that for a null status you get some result (assuming you want this)
Assert.assertNotNull(obj.findOverviewByStatus(null));
// Lets assume that a null status returns an empty array, add a check for it
Assert.assertTrue("null parameter size should be 0", obj.findOverviewByStatus(null).size() == 0);
//etc...
}
}

Can string and variable name be different in queryparam annotation in java?

Here is a scenario.
Case1: (#QueryParam("username") String username)
URL: example?username=yourname
Case2: (#QueryParam("username") String name)
URL: example?username=yourname
In these 2 cases which is correct way of using QueryParam.
When I use Case1, it works, yourname is printed. But when I use Case2, null is printed.
I want to implement Case 2 because of following some coding standards. Means I do not want to change the variable name (which is "String name"). But in URL I have to use "username".
Is there any way of using Case2 scenario for QueryParam.
Adding code(1st edit)
Here is the code which I replicated the issue that I am facing
#RequestMapping (value = "/username1", method = RequestMethod.GET)
#ResponseBody
public Response username1(#QueryParam("username") String username) {
System.out.println("Username1 is ==> " + username);
return Response.ok(username).build();
}
#RequestMapping (value = "/username2", method = RequestMethod.GET)
#ResponseBody
public Response username2(#QueryParam("username") String name) {
System.out.println("Username2 is ==> " + name);
return Response.ok(name).build();
}
/username1?username=yourname
Output: Username1 is ==> yourname
/username2?username=yourname
Output: Username2 is ==> null
Thank you
Yes, you're doing something wrong: you're using QueryParam, which is a JAX-RS annotation, in a Spring-MVC application.
The equivalent Spring annotation is RequestParam. JAX-RS and Spring MVC are two different things. You can't just use the annotations of one in the other.

How to unit tests method returning Java string?

Hi I have plenty of methods which returns string which I need to unit tests. Now challenge for me is is how do I do code coverage? For e.g. My method looks like the following
Public String getSqlToDoXYZ(Myconfig config) {
StringBuilder sb = new StringBuilder() ;
sb.append( "SELECT BLAH BLAH");
sb.append("WHERE ABC="+config.getABC());
return sb.toString() ;
}
Please give your ideas how to unit tests such scenario? I am using mockito to do unit testing. I am new to both mockito and unit testing please guide. Thanks in advance.
As pointed elsewhere, using Strings for SQL statements is dangerous, regarding SQL injections.
That being said, you simply have to call the method, and compare the result to a String you build "by hand" :
public void testBuildSqlToDoXYZ() throws Exception {
WhateverYourObjectIs yourObject = new WhateverYourObjectIs();
MyConfig myConfig = new MyConfig();
myConfig.setABC("foo");
String sql = yourObject.getSqlToDoXYZ(myConfig);
assertEquals("SELECT BLAH BLAH WHERE ABC='foo'", sql);
}
Mockito would only be helpfull in this case if is impossible for you to create a "MyConfig" object by hand.
There might be something else in your code that makes that difficult, but you haven't really provided us enough info.
I can't see any difficulties to test a method that returns a string.
public void testSqlToDoXYZ()
{
MyObj testTarget = new MyObj(/*Parameters if needed*/);
Config config = new Config();
/*Config config = new ConfigMock();*/
String expectedResult = "SELECT BLAH BLACH WHERE ABC=123;";
assertEquals(epxectedResult, testTarget.getSqlToDoXYZ(config));
}
The key is, that you have to know your result. If the config.getABC() call could return different values, you have to make sure it always provides the same values for your test. So you might have to mock your config object or put fake data into your config provider.

Categories