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.");
Related
I'm a bit lost when playing with Mutiny. I have a testfunction that Creates a String uni and when that is succesful I want that it return a 200 OK or when it fails(which it shouldn´t with this simple string creation) a 500 Internal Server Error for now.
I can get it to work by using the onItemOrOnFailure() of mutiny but I'm trying to split up the handling of the success and failure scenario. I see my sout of the onItem() but I still get to the onFailure() and get a 500 response in postman. Why do I go into the onFailure? What am I not understanding?
So I expect one or the other but I get into both.
#GET
#Path("/test")
#Produces(MediaType.APPLICATION_JSON)
public Uni<RestResponse<?>> test() {
return Uni.createFrom().item("Hello world")
.onItem().transform(str -> {
var resp = RestResponse.ok(str);
System.out.println("In onItem");
return resp;
})
.onFailure().recoverWithNull().replaceWith(() -> {
System.out.println("In onFailure");
return RestResponse.status(500);
});
}
I think I figured it out. Its not the fault of the onFailure, but its just that the replaceWith isnt part of the onFailure. Its just always called as the next part.
So on success it goes onItem -> transform -> replaceWith and onFailure it goes onFailure -> recoverWithNull -> replaceWith.
So If i understand correctly; when using a recoverWith... function you step out of the failure event and continue with the rest of the steps Is this a correct assumption?
Ah well what a bit of sleep can do:)
I'm trying to reactively fetch data from external API using two methods from some Service class.
I'm new to reactive and Spring in general, so it could be a very obvious mistake but I just can't find it
These are the two methods:
public Mono<SomeClass> get(int value) {
return webClient.get()
.uri("/" + value)
.retrieve()
.onRawStatus(HttpStatus.CONFLICT::equals, clientResponse -> {
return Mono.error(new SomeException1("Some message", clientResponse.rawStatusCode()));
})
.onRawStatus(HttpStatus.NOT_FOUND::equals, clientResponse -> {
return requestGeneration(value)
.flatMap(res -> Mono.error(new SomeException1("Some message", clientResponse.rawStatusCode())));
})
.bodyToMono(SomeClass.class)
.retryWhen(Retry.backoff(5, Duration.ofSeconds(8))
.filter(throwable -> throwable instanceof SomeException1));
}
private Mono<Void> requestGeneration(int value) {
return webClient.post()
.uri("/" + value)
.retrieve()
.onRawStatus(HttpStatus.BAD_REQUEST::equals, clientResponse -> {
return Mono.error(new SomeException2("Wrong value", clientResponse.rawStatusCode()));
})
.bodyToMono(Void.class);
}
Baasically what I'm trying to achieve is:
first GET from http://api.examplepage.com/{value}
if that API returns HTTP404 it means I need to first call POST to the same URL, because the data is not yet generated
the second function does the POST call and returns Mono<Void> because it is just HTTP200 or HTTP400 on bad generation seed (i don't need to process the response)
first function (GET call) could also return HTTP429 which means the data is generating right now, so I need to call again after some time period (5-300 seconds) and check if data has been generated already
then after some time it results in HTTP200 with generated data which I want to map to SomeClass and then return mapped data in controller below
#PostMapping("/follow/{value}")
public Mono<ResponseEntity<?>> someFunction(#PathVariable int value) {
return Mono.just(ResponseEntity.ok(service.get(value)));
}
all the code I posted is very simplified to the issues I'm struggling with and doesn't contain some things I think are not important in this question
and now the actual question:
it doesn't actually make the call? i really don't know what is happening
program doesn't enter onRawStatus, even if i change it to onStatus 2xx or whatever other httpstatus and log inside i see nothing as if it doesn't even enter the chains
when i manually call with postman it seems like the program calls have never been made because the GET call returns 404 (the program didn't request to generate data)
the controller only returns "scanAvailable": true, when i expect it to return mapped SomeClass json
// edit
i changed the code to be a full chain as suggested and it didn't solve the problem. all the status are still unreachable (code inside any onStatus nor onRawStatus never executes)
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.
I am very new to Java and Mutiny.
As indicated below, my test function asks Redis for the value of key "foo" which is "bar". That is working and the Future onCompleted() gets "bar".
So far so good.
I have two issues with the Uni.createFrom().future() bit.
The compiler says: The method future(Future<? extends T>) in the type UniCreate is not applicable for the arguments (Future<Response>). I have tried the suggested fixes but ended up in a deeper hole. My Java skills are insufficient to fully grasp the meaning of the error.
How do I get "bar" into the Uni<String> returned from test()? I have tried all sorts of subscribing and CompletableFutures and cannot make anything work. I figure I need to return a function to generate the Uni but am at a loss about how to do that.
// The abbreviated setup
import io.vertx.redis.client.Redis;
private final Redis redisClient;
this.redisClient = Redis.createClient(vertx);
public Uni<String> test () {
// Ask Redis for the value of key "foo" => "bar"
Future<Response> futureResponse = this.redisClient.send(Request.cmd(Command.create("JSON.GET")).arg("foo"))
.compose(response -> {
// response == 'bar'
return Future.succeededFuture(response);
}).onComplete(res -> {
// res == 'bar'
});
// How to make the return of the Uni<String> wait for the completed futureResponse
// so it makes a Uni<String> from "bar" and returns it from the method?
Uni<String> respUni = Uni.createFrom().future(futureResponse);
return respUni;
}
Thanks. Any suggestions gratefully accepted! (And yes, I have spent many hours trying to work it out for myself) ;-)
Updated the post, because of errors.
UniCreate.future() takes a java.util.concurrent.Future of some type and returns Uni of the same type. That is, you'll have to pass a java.util.concurrent.Future<String> to get a Uni<String>.
The send method of the Redis client returns a io.vertx.core.Future<Response> which is not assignment compatible to java.util.concurrent.Future.
Fortunately, the API provides io.vertx.core.Future#toCompletionStage to convert a vertx Future to a JDK CompletionStage while Mutiny provides UniCreate.completionStage() to get the job done.
public Uni<String> test () {
Future<String> futureResponse = this.redisClient.send(Request.cmd(Command.create("JSON.GET")).arg("foo"))
.compose(response -> {
return Future.succeededFuture(response.toString());
});
Uni<String> respUni = Uni.createFrom().completionStage(futureResponse.toCompletionStage());
return respUni;
}
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...
}
}