I'm building a microservice using Spring Boot. I wrote an API with GET-, POST-, PUT-, DELETE- Methods, run the application and tested it using Postman - everything's working fine...
But testing the PUT-Method fails with
java.lang.AssertionError: Status expected:<204> but was:<400>
Running the test in debug-mode and stepping throw throws an InvocationTargetException:
My RestController-Methods looks like this:
#PutMapping(value = "/{id}")
public ResponseEntity updateSongById(#PathVariable("id") Integer id, #RequestBody #Validated
SongDto songDto) {
// TODO Add authorization
SongDto song = songService.getSongById(id);
if (song == null)
return new ResponseEntity(HttpStatus.BAD_REQUEST);
return new ResponseEntity(songService.updateSong(id, songDto), HttpStatus.NO_CONTENT);
}
songService.getSongById(id):
#Override
public SongDto getSongById(Integer id) {
return songMapper.songToSongDto(songRepository.findById(id)
.orElseThrow(NotFoundException::new));
}
The SongRepository is just a simple Interface which extends JpaRepository<Song, Integer>.
My failing test looks like this:
#Test
void updateSongById_success() throws Exception {
when(songService.updateSong(anyInt(), any(SongDto.class))).thenReturn(getValidSongDto());
String songDtoJson = objectMapper.writeValueAsString(getValidSongDto());
mockMvc.perform(put("/rest/v1/songs/1")
.content(songDtoJson)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());
}
And getValidSongDto() just provides a Dto used in my tests:
private SongDto getValidSongDto() {
return SongDto.builder()
.id(1)
.title("TestSongValid")
.label("TestLabelValid")
.genre("TestGenreValid")
.artist("TestArtistValid")
.released(1000)
.build();
}
I really don't understand at the moment, what I did wrong to make this test fail and also couldn't find anything in the internet which helped me solving this problem, so far. So, therefor I'd be very thankful, if anybody could tell me what's wrong here and how to solve this issue.
Thank you very much!!
You need to return the value for songService.getSongById as shown below
#Test
void updateSongById_success() throws Exception {
when(songService.getSongById(Mockito.any())).thenReturn(getValidSongDto());
when(songService.updateSong(anyInt(), any(SongDto.class))).thenReturn(getValidSongDto());
String songDtoJson = objectMapper.writeValueAsString(getValidSongDto());
mockMvc.perform(put("/rest/v1/songs/1")
.content(songDtoJson)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());
}
Related
I have taken the web-api-service-example and is trying to adapt it to a Mongo based backend. The code looks almost identical except I have added (duplicate/rename) the Transaction.class to a User.class as well as the Service implementation and web handler.
I am trying to get the user json from the URL /users/username. Mongo correctly retrieves it.
My question is this: I have two pieces of code, one working (returning the user json) and one not working throwing a NPE.
Not working code (it compiles and I cannot understand why is not working):
#Override
public void getUser(
String username,
OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler) {
resultHandler.handle(
persistence.getUser(username)
.compose(user -> {
if (user.isPresent()) {
System.err.println("D: " + user.get());
return Future.succeededFuture(OperationResponse.completedWithJson(user.get()));
} else {
return Future.succeededFuture(new OperationResponse().setStatusCode(404).setStatusMessage("Not Found"));
}
})
);
}
It thows the following exception:
SEVERE: Failed to handleMessage. address: __vertx.reply.1
java.lang.NullPointerException
at io.vertx.ext.web.api.OperationResponseConverter.fromJson(OperationResponseConverter.java:15)
at io.vertx.ext.web.api.OperationResponse.<init>(OperationResponse.java:30)
at io.vertx.ext.web.api.contract.impl.RouteToEBServiceHandler.lambda$handle$1(RouteToEBServiceHandler.java:35)
at io.vertx.core.eventbus.impl.EventBusImpl.lambda$convertHandler$2(EventBusImpl.java:342)
at io.vertx.core.eventbus.impl.HandlerRegistration.deliver(HandlerRegistration.java:278)
...
...
D: {"_id":"5fbebeb97d3c4c0e48f9c7e0","email":"some#user.dk","firstName":"firstname","id":0,"lastName":"lastname","password":"password","phone":"+4512345678","username":"username"}
The NPE is thrown first, then the System.err.println("D: " + user.get()); gets executed and prints the user. The browser then waits forever for a reply.
When this piece of code working fine:
#Override
public void getUser(
String username,
OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler) {
persistence.getUser(username).compose(user -> {
if (user.isPresent()) {
resultHandler.handle(Future.succeededFuture(OperationResponse.completedWithJson(user.get())));
return Future.succeededFuture();
} else {
resultHandler.handle(Future.succeededFuture(new OperationResponse().setStatusCode(404).setStatusMessage("Not Found")));
return Future.failedFuture("Error");
}
});
}
One would presume that returing the Future.succeededFuture() to the resultHandler.handle() would be the correct way to do it? But it throws the NPE!
I got it working using this getUser() persistance method:
#Override
public Future<JsonObject> getUser(String username) {
Promise<JsonObject> promise = Promise.promise();
MongoClient mongoClient = MongoClient.createShared(vertx, mongoconfig);
JsonObject query = new JsonObject().put("username", username);
mongoClient.findOne(collectionName, query, null, promise::handle);
return promise.future();
}
and this service handler:
#Override
public void getUser(
String username,
OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler) {
persistence.getUser(username)
.onSuccess(jsonObject -> {
resultHandler.handle(Future.succeededFuture(OperationResponse.completedWithJson(jsonObject)));
});
}
Great code comes with great responsability... One has to love Futures (once understood) :)
I do have a problem with redirect attribute in my spring web app method. Here's the method:
#RequestMapping("/employee")
public String saveEmployee(#ModelAttribute EmployeeCommand commandObject){
EmployeeCommand savedCommand = employeeService.saveEmployeeCommand(commandObject);
return "redirect:/employee/" + savedCommand.getId();
}
I have written a test:
#Test
public void saveEmployee() throws Exception {
//given
EmployeeCommand employeeCommand = new EmployeeCommand();
employeeCommand.setId(2L);
//when
when(employeeService.saveEmployeeCommand(employeeCommand)).thenReturn(employeeCommand);
//then
mockMvc.perform(post("/employee"))
.andExpect(status().isOk());
.andExpect(redirectedUrl("redirect:/employee/2"));
verify(employeeService, times(1)).saveEmployeeCommand(employeeCommand);
}
I keep getting a NullPointerException related to that line:
return "redirect:/employee/" + savedCommand.getId();
It seems that savedCommand is null. Any tips?
Try to use
mockMvc.perform(post("/employee")).flashAttr("commandObject", employeeCommand));
Right now you are only perfmorming post request without passing commandObject and therefore this mock:
when(employeeService.saveEmployeeCommand(employeeCommand)).thenReturn(employeeCommand);
Does not trigger
when I was sync I wrote unit tests mocking the persistence part and check the caller's behavior. Here is an example about what I usually did:
#Mock
private OfferPersistenceServiceImpl persistenceService;
#Inject
#InjectMocks
private OfferServiceImpl offerService;
...
#Test
public void createInvalidOffer() {
offer = new Offer(null, null, null, null, null, 4, 200D, 90D);
String expectedMessage = Offer.class.getName() + " is not valid: " + offer.toString();
Mockito.when(persistenceService.create(offer)).thenThrow(new IllegalArgumentException(expectedMessage));
Response response = offerService.create(offer);
Mockito.verify(persistenceService, Mockito.times(1)).create(offer);
Assert.assertEquals(INVALID_INPUT, response.getStatus());
String actualMessage = response.getEntity().toString();
Assert.assertEquals(expectedMessage, actualMessage);
}
But now I fell in love with Vertx.io (to which I am pretty new) and I want to be async. Nice. But Vertx has handlers, so the new persistence component to mock looks like this:
...
mongoClient.insert(COLLECTION, offer, h-> {
...
});
So I am guessing how to mock handler h to tests class who's using that mongoClient or even if it is the right way to test with Vertx.io. I am using vertx.io 3.5.0, junit 4.12 and mockito 2.13.0. Thanks.
Update
I tried to follow tsegimond suggestion but I can't get how Mockito's Answer and ArgumentCaptor can help me. Here is what I tried so far.
Using ArgumentCaptor:
JsonObject offer = Mockito.mock(JsonObject.class);
Mockito.when(msg.body()).thenReturn(offer);
Mockito.doNothing().when(offerMongo).validate(offer);
RuntimeException rex = new RuntimeException("some message");
...
ArgumentCaptor<Handler<AsyncResult<String>>> handlerCaptor =
ArgumentCaptor.forClass(Handler.class);
ArgumentCaptor<AsyncResult<String>> asyncResultCaptor =
ArgumentCaptor.forClass(AsyncResult.class);
offerMongo.create(msg);
Mockito.verify(mongoClient,
Mockito.times(1)).insert(Mockito.anyString(), Mockito.any(), handlerCaptor.capture());
Mockito.verify(handlerCaptor.getValue(),
Mockito.times(1)).handle(asyncResultCaptor.capture());
Mockito.when(asyncResultCaptor.getValue().succeeded()).thenReturn(false);
Mockito.when(asyncResultCaptor.getValue().cause()).thenReturn(rex);
Assert.assertEquals(Json.encode(rex), msg.body().encode());
and using Answer:
ArgumentCaptor<AsyncResult<String>> handlerCaptor =
ArgumentCaptor.forClass(AsyncResult.class);
AsyncResult<String> result = Mockito.mock(AsyncResult.class);
Mockito.when(result.succeeded()).thenReturn(true);
Mockito.when(result.cause()).thenReturn(rex);
Mockito.doAnswer(new Answer<MongoClient>() {
#Override
public MongoClient answer(InvocationOnMock invocation) throws Throwable {
((Handler<AsyncResult<String>>)
invocation.getArguments()[2]).handle(handlerCaptor.capture());
return null;
}
}).when(mongoClient).insert(Mockito.anyString(), Mockito.any(),
Mockito.any());
userMongo.create(msg);
Assert.assertEquals(Json.encode(rex), msg.body().encode());
And now I got confused. Is there a way to mock an AsyncResult to let it return false on succeed()?
Finally I got some times to investigate and I made it. Here is my solution.
#RunWith(PowerMockRunner.class)
#PowerMockRunnerDelegate(VertxUnitRunner.class)
#PrepareForTest({ MongoClient.class })
public class PersistenceTest {
private MongoClient mongo;
private Vertx vertx;
#Before
public void initSingleTest(TestContext ctx) throws Exception {
vertx = Vertx.vertx();
mongo = Mockito.mock(MongoClient.class);
PowerMockito.mockStatic(MongoClient.class);
PowerMockito.when(MongoClient.createShared(Mockito.any(), Mockito.any())).thenReturn(mongo);
vertx.deployVerticle(Persistence.class, new DeploymentOptions(), ctx.asyncAssertSuccess());
}
#SuppressWarnings("unchecked")
#Test
public void loadSomeDocs(TestContext ctx) {
Doc expected = new Doc();
expected.setName("report");
expected.setPreview("loremipsum");
Message<JsonObject> msg = Mockito.mock(Message.class);
Mockito.when(msg.body()).thenReturn(JsonObject.mapFrom(expected));
JsonObject result = new JsonObject().put("name", "report").put("preview", "loremipsum");
AsyncResult<JsonObject> asyncResult = Mockito.mock(AsyncResult.class);
Mockito.when(asyncResult.succeeded()).thenReturn(true);
Mockito.when(asyncResult.result()).thenReturn(result);
Mockito.doAnswer(new Answer<AsyncResult<JsonObject>>() {
#Override
public AsyncResult<JsonObject> answer(InvocationOnMock arg0) throws Throwable {
((Handler<AsyncResult<JsonObject>>) arg0.getArgument(3)).handle(asyncResult);
return null;
}
}).when(mongo).findOne(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
Async async = ctx.async();
vertx.eventBus().send("persistence", new JsonObject(), msgh -> {
if (msgh.failed()) {
System.out.println(msgh.cause().getMessage());
}
ctx.assertTrue(msgh.succeeded());
ctx.assertEquals(expected, Json.decodeValue(msgh.result().body().toString(), Doc.class));
async.complete();
});
async.await();
}
}
Use Powemockito to mock the MongoClient.createShared static method, so you'll have your mock when verticle starts. Mocking async handler is a bit of code to write. As you can see mocking start at Message<JsonObject> msg = Mockito.mock(Message.class); and ends at Mockito.doAnswer(new Answer.... In the Answer's method pick the handler param and force it to handle your async result then you're done.
Normally, I'd use a comment to post this, but formatting gets lost. The accepted solution is works great, just note that it can be simplified a bit using Java 8+, and you can use your actual objects instead of JSON.
doAnswer((Answer<AsyncResult<List<Sample>>>) arguments -> {
((Handler<AsyncResult<List<Sample>>>) arguments.getArgument(1)).handle(asyncResult);
return null;
}).when(sampleService).findSamplesBySampleFilter(any(), any());
getArgument(1), refers to the index of the handler argument in a method such as:
#Fluent
#Nonnull
SampleService findSamplesBySampleFilter(#Nonnull final SampleFilter sampleFilter,
#Nonnull final Handler<AsyncResult<List<Sample>>> resultHandler);
Everything seems to be right in my test but I keep getting this error on the book service call to find all
i have mocked the class i am using correctly .
#Test
#WithMockUser(username = "admin", roles={"ADMIN"})
public void bookRemoveTest() throws Exception {
Book book = new Book();
List<Book> expectedBookList = createBookList(10);
/* expect */ bookService.removeOne(anyLong());
EasyMock.expectLastCall();
replay(bookService);
bookService.removeOne(anyLong());
// Assert.assertEquals("expectedBookList", bookService.findAll());
expect(bookService.findAll()).andReturn(expectedBookList);
mockMvc
.perform(post("/book/remove")
.accept(MediaType.TEXT_HTML)
.contentType(MediaType.TEXT_HTML)
.param("id","12345678" ))
.andExpect(model().attributeExists("bookList"))
.andExpect(view().name("bookList"))
.andExpect(content().contentType(MediaType.TEXT_HTML))
.andReturn();
}
You should call replay method after you finished the setup.
So please move the expect(bookService.findAll()) call before the replay(bookService) and it should be good.
I am trying to add a restful api to a java microservice. For this, I am using spark:
http://sparkjava.com/documentation.html
I've created a very simple class which stands up an api. That class is here:
public class Routes {
public void establishRoutes(){
get("/test", (req, res) -> "Hello World");
after((req, res) -> {
res.type("application/json");
});
exception(IllegalArgumentException.class, (e, req, res) -> {
res.status(400);
});
}
Now, running Routes.establishRoutes() should stand up an api which would show "Hello World" in the event someone decides to visit http://localhost:4567/test. This does actually work. Hurray!
The next step is unit testing the code. My unit test, unfortunately, does not succeed. The spark documentation does not detail a sound way for doing testing so what I have is pieced together from examples I found around the net. Here is my Junit test:
public class TestRoutes {
#Before
public void setUp() throws Exception {
Routes newRoutes = new Routes();
newRoutes.establishRoutes();
}
#After
public void tearDown() throws Exception {
stop();
}
#Test
public void testModelObjectsPOST(){
String testUrl = "/test";
ApiTestUtils.TestResponse res = ApiTestUtils.request("GET", testUrl, null);
Map<String, String> json = res.json();
assertEquals(201, res.status);
}
Here is the code behind ApiTestUtils.request():
public class ApiTestUtils {
public static TestResponse request(String method, String path, String requestBody) {
try {
URL url = new URL("http://localhost:4567" + path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod(method);
connection.setDoOutput(true);
connection.connect();
String body = IOUtils.toString(connection.getInputStream());
return new TestResponse(connection.getResponseCode(), body);
} catch (IOException e) {
e.printStackTrace();
fail("Sending request failed: " + e.getMessage());
return null;
}
}
public static class TestResponse {
public final String body;
public final int status;
public TestResponse(int status, String body) {
this.status = status;
this.body = body;
}
public Map<String,String> json() {
return new Gson().fromJson(body, HashMap.class);
}
}
}
I am failing on connection.connect() inside ApiTestUtils.request(). Specifically, I get the error: java.lang.AssertionError: Sending request failed: Connection refused
I believe this is happening because the application isn't listening when my test tries to make the request. However, I don't understand why that would be the case. I borrowed the test code from the demo project found here:
https://github.com/mscharhag/blog-examples/blob/master/sparkdemo/src/test/java/com/mscharhag/sparkdemo/UserControllerIntegrationTest.java
UPDATE:
I tried running the example linked above. Turns out, it doesn't work either. Looks like spinning up a spark instance in this context is more difficult than I thought? I'm not trying to figure out how to do so.
In your test case is missing the code used for waiting the initialization of the embedded server.
I've tried your code and stumbled on the same issue as you did, but after debugging it I've noticed that the embedded spark server is initialized in a newly created thread. ( see the method spark.Service#init()).
All you need to do in your test is to await for the initialization by calling the method spark.Spark#awaitInitialization()
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static junit.framework.TestCase.assertEquals;
import static spark.Spark.awaitInitialization;
import static spark.Spark.stop;
public class TestRoutes {
#Before
public void setUp() throws Exception {
Routes newRoutes = new Routes();
newRoutes.establishRoutes();
awaitInitialization();
}
#After
public void tearDown() throws Exception {
stop();
}
#Test
public void testModelObjectsPOST() {
String testUrl = "/test";
ApiTestUtils.TestResponse res = ApiTestUtils.request("GET", testUrl, null);
assertEquals(200, res.status);
}
}
I can't help you with Spark, but if you're open to trying an alternative lightweight Java library for writing micro services, have a look at Molecule.
You'll find documentation and examples for writing unit tests and integration tests. If I understand what you're trying to test correctly, the test would look like:
HttpRequest request = new HttpRequest(4567);
HttpResponse response = request.get("/test");
assertThat(response).hasStatusCode(201)
.hasContentType("application/json")
.hasBodyText("Hello World");