Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support AssertJ variant in MockMvc [SPR-16637] #21178

Closed
spring-projects-issues opened this issue Mar 23, 2018 · 26 comments
Closed

Support AssertJ variant in MockMvc [SPR-16637] #21178

spring-projects-issues opened this issue Mar 23, 2018 · 26 comments
Assignees
Labels
in: test Issues in the test module type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

Brian Clozel opened SPR-16637 and commented

This Spring Boot issue shows that many developers would like to use AssertJ-style assertions with MockMvc. This would also be a nice complement to the existing Kotlin support, since Kotlin users seem to favor this style of assertions.

The linked Spring Boot issue contains a fairly advanced branch with a working prototype.


Reference URL: spring-projects/spring-boot#5729

8 votes, 12 watchers

@spring-projects-issues spring-projects-issues added in: test Issues in the test module type: enhancement A general enhancement labels Jan 11, 2019
@spring-projects-issues spring-projects-issues added this to the 5.x Backlog milestone Jan 11, 2019
@ngeor
Copy link

ngeor commented Jan 28, 2019

I've put together a library that offers AssertJ assertions for MockMvc but also for ResponseEntity (returned by TestRestTemplate): https://github.com/ngeor/yak4j-spring-test-utils

@manueljordan
Copy link

I have read the comments in the other post (about SB), why not work around two branches? One for Hamcrest (Keeping the current available approach) and the other one for AssertJ ... Sadly Hamcrest is very verbose to test data about returned data either in XML or JSON.

@manueljordan
Copy link

Hello @sbrannen here for your consideration. Thanks for your understanding

@cassiomolin
Copy link

+1
Support for AssertJ assertions would be great!

@sbrannen
Copy link
Member

I've been thinking about this, and I'm not sure that we need to add explicit support for AssertJ in order to allow people to choose to use AssertJ, Truth, or any other assertion framework.

For example, if we introduce generic "consumer" support (as in Consumer<T>), we might be able to do something like the following.

mockMvc.perform(get("/users"))
    .andExpect(model().attribute("userList", List.class),
        list -> assertThat(list).hasSize(2)));
  • That attribute() method would have a signature like <T> T attribute(String name, Class<T> requiredType).
  • That andExpect() method would have a signature like andExpect(T object, Consumer<T> consumer). Though perhaps something like andConsume() would make more sense for such a use case.
  • assertThat(list) is from AssertJ in this example.

@sbrannen
Copy link
Member

sbrannen commented Jul 18, 2019

A quick local spike with the following new method in ModelResultMatchers...

public <T> ResultMatcher attribute(String name, Class<T> requiredType, Consumer<T> valueConsumer) {
	return result -> {
		ModelAndView mav = getModelAndView(result);
		T value = (T) ClassUtils.resolvePrimitiveIfNecessary(requiredType).cast(mav.getModel().get(name));
		valueConsumer.accept(value);
	};
}

... results in the following working test case, modified from Spring's test suite.

@Test
public void testAttributeEqualTo() throws Exception {
	mockMvc.perform(get("/"))
		// Hamcrest
		.andExpect(model().attribute("integer", equalTo(3)))
		// AssertJ
		.andExpect(model().attribute("integer", int.class, num -> assertThat(num).isEqualTo(3)));
}

The "consumer" variant is obviously considerably more verbose, but it shows that it's possible to provide such generic functionality with the current building blocks in MockMvc.

@philwebb
Copy link
Member

philwebb commented Jul 18, 2019

That's good to know we could provide hooks. One of the more annoying aspects of trying to create this type of assertion is that you really need to expose AssertProvider in your API. There's no easy way I found of making mockMvc directly assertable. In my earlier spike I ended up creating a new MvcTester class that was AssertJ specific. That makes the tests look nice but means it wont work without AssertJ on the classpath.

@sbrannen
Copy link
Member

That makes the tests look nice but means it wont work without AssertJ on the classpath.

Indeed, it does look nice: fluent!

But I agree that forcing people to have AssertJ on the classpath is a major drawback. So, if we do anything AssertJ-specific, we should do our best to ensure that AssertJ remains an optional dependency.

@123Haynes
Copy link

@philwebb would it be possible to use a wrapper class that checks if AssertJ is available on the classpath and if not, falls back to hamcrest? That way AssertJ could remain optional.

Something like https://stackoverflow.com/questions/11432212/java-class-with-possibly-missing-run-time-dependencies

@sbrannen
Copy link
Member

would it be possible to use a wrapper class that checks if AssertJ is available on the classpath and if not, falls back to hamcrest?

I'm not sure what you mean by falling back to Hamcrest.

The Hamcrest and AssertJ APIs are not compatible. So any explicit support for AssertJ would have to be in addition to the existing Hamcrest support.

@123Haynes
Copy link

I'm not sure what you mean by falling back to Hamcrest.

The Hamcrest and AssertJ APIs are not compatible. So any explicit support for AssertJ would have to be in addition to the existing Hamcrest support.

Sorry. Wrong wording. I was just trying to propose a possible solution that would keep AssertJ optional.

@manueljordan
Copy link

Hello Sam

The Hamcrest and AssertJ APIs are not compatible. So any explicit support for AssertJ would have to be in addition to the existing Hamcrest support.

Even when they are not compatible, seems an important goal is create a common API to change smoothly a technology to other quickly (i.e: Hamcrest to AssertJ) and let add a new technology (i.e:Truth or other in the future). Seems Consumer<T> plays an important role.

You and Phill are the experts ...

If is not possible create a common API (seems is very hard for this situation) perhaps JUnit 5 would play an important role how a kind of Adapter? I don't know, but the option to create a new module playing and bringing support how an adapter. Or consider create this adapter in Spring Test module

@philwebb
Copy link
Member

would it be possible to use a wrapper class that checks if AssertJ is available on the classpath and if not, falls back to hamcrest

Unfortunately not because we need a specific type available at compile time so that the IDE can offer code completion. I faced the same problem with JsonTester in Spring Boot.

If we do anything AssertJ-specific, we should do our best to ensure that AssertJ remains an optional dependency.

That probably means we either need an AssertJ specific version of MockMvc, or we end up with a sub-par API. I'd probably prefer moving this back to Spring Boot if can't make the API fluent. We've already got much stronger opinions about using AssertJ so it might not feel so forced there.

@sbrannen
Copy link
Member

That probably means we either need an AssertJ specific version of MockMvc, or we end up with a sub-par API.

Right. To achieve a decent API (fluent with code completion a la AssertJ's traditional style), we would either need a specific version of MockMvc, or we would need to branch into a "parallel version" of the existing functionality that offers AssertJ APIs instead of Hamcrest.

For example, after the invocation of mockMvc.perform(get("/")), we could introduce a branch -- for example mockMvc.perform(get("/")).fluent(), mockMvc.perform(get("/")).assertJ(), or something similar -- and from that point on the API would be fluent and AssertJ-based. If the user never "branches", we hopefully should be able to make AssertJ an optional dependency.

I'd probably prefer moving this back to Spring Boot if can't make the API fluent. We've already got much stronger opinions about using AssertJ so it might not feel so forced there.

I still think that the "generic consumer" approach I outlined is worth considering, regardless of whether we support AssertJ directly in spring-test.

@philwebb
Copy link
Member

For example, after the invocation of mockMvc.perform(get("/")), we could introduce a branch -- for example mockMvc.perform(get("/")).fluent(), mockMvc.perform(get("/")).assertJ(), or something

I'd need to brush up on my classloading knowledge. If fluent() returns a MvcFluent type that extends an AssertJ interface does that mean people can still call mockMvc.perform(get("/")) if they don't have the AssertJ jar?

@sbrannen
Copy link
Member

I'd need to brush up on my classloading knowledge. If fluent() returns a MvcFluent type that extends an AssertJ interface does that mean people can still call mockMvc.perform(get("/")) if they don't have the AssertJ jar?

You might be right: that might not work at all. I'd have to investigate. We do have similar "tricks" in various places in the framework where we work with an "optional" type only-on-demand, but the difference might be that we don't expose the "optional" type directly in those use cases. So I might be confusing those tricks with the scenario here.

sbrannen added a commit to sbrannen/spring-framework that referenced this issue Jul 20, 2019
@sbrannen
Copy link
Member

The branching approach seems to work fine.

Check out the following feature branch to see it in action.

https://github.com/sbrannen/spring-framework/commits/issues/gh-21178-mock-mvc-assertj-playground

Overview:

  1. new MvcFluent API that declares a method with an AssertJ return type -- UriAssert just as a proof of concept
  2. new MvcFluent fluent() method in ResultActions
  3. new spring-test-test module that depends on spring-test and uses MockMvc but does not have a dependency on AssertJ
  4. MockMvcTests test case in the spring-test-test module that uses the fluent() API

Tests:

  • getPerson42(): uses MockMvc without fluent() API -- everything fine
  • fluentAndReturn(): uses MockMvc with the fluent() API but only invoking methods that do not return AssertJ types (i.e., andReturn()) -- everything fine
  • fluentAndAssertThat(): uses MockMvc with the fluent() API and invokes a method that returns an AssertJ type (i.e., assertThat(URI)) -- results in an compiler error as expected. For example, in Eclipse: "The method assertThat(URI) from the type MvcFluent refers to the missing type UriAssert".

@sbrannen
Copy link
Member

If fluent() returns a MvcFluent type that extends an AssertJ interface does that mean people can still call mockMvc.perform(get("/")) if they don't have the AssertJ jar?

Please note that in my proof of concept, MvcFluent does not extend an AssertJ interface. Rather, MvcFluent declares methods that have AssertJ return types.

@sbrannen
Copy link
Member

Regarding generic assertion hooks in MockMvc, see gh-23330.

@nightswimmings
Copy link

nightswimmings commented Dec 1, 2019

Will this be possible in both MockMvcResultMatchers and MockRestRequestMatchers? That would be amazing, the world is so tired of Hamcrest

@sbrannen
Copy link
Member

sbrannen commented Dec 2, 2019

Will this be possible in both MockMvcResultMatchers and MockRestRequestMatchers?

If we do something for MockMvc, it should also be possible to do something similar for MockRestServiceServer.

@ccostin93
Copy link

Are there any updates regarding assertj support?

@sbrannen
Copy link
Member

Are there any updates regarding assertj support?

This is still in the 5.x Backlog for future consideration.

@mteodori
Copy link

are there any workarounds to use assertj or even an alternative to mockmvc that supports it?

@sbrannen
Copy link
Member

are there any workarounds to use assertj or even an alternative to mockmvc that supports it?

You might be happy with the MockMvcWebTestClient variant of the WebTestClient. It does not use AssertJ, but it does have a fluent API and supports MockMvc under the hood.

@sbrannen sbrannen added status: pending-design-work Needs design work before any code can be developed and removed for: team-attention labels Feb 21, 2023
@snicoll snicoll removed the status: pending-design-work Needs design work before any code can be developed label Jan 19, 2024
@snicoll snicoll modified the milestones: 6.x Backlog, 6.2.0-M1 Feb 1, 2024
snicoll added a commit that referenced this issue Mar 15, 2024
This commit moves JSON content AssertJ support from Spring Boot.

See gh-21178

Co-authored-by: Brian Clozel <[email protected]>
snicoll added a commit that referenced this issue Mar 15, 2024
This commit moves JSON path AssertJ support from Spring Boot.

See gh-21178

Co-authored-by: Brian Clozel <[email protected]>
snicoll added a commit that referenced this issue Mar 15, 2024
This commit adds AssertJ compatible assertions for HTTP request and
responses, including headers, media type, and response body. The latter
delegate to the recently included JSON assert support.

See gh-21178
snicoll added a commit that referenced this issue Mar 15, 2024
This commit adds AssertJ compatible assertions for the component that
produces the result from the request.

See gh-21178
snicoll added a commit that referenced this issue Mar 15, 2024
This commit adds AssertJ compatible assertions for the Model that is
generated from an HTTP request.

See gh-21178
snicoll added a commit that referenced this issue Mar 15, 2024
This commit adds AssertJ compatible assertions for cookies

See gh-21178

Co-authored-by: Brian Clozel <[email protected]>
snicoll added a commit that referenced this issue Mar 15, 2024
This commit adds a new way to use MockMvc by returning a MvcResult that
is AssertJ compatible. Compared to the existing MockMvc infrastructure,
this new model has the following advantages:

* Can be created from a MockMvc instance
* Dedicated builder methods for easier setup
* Assertions use familiar AssertJ syntax with discoverable assertions
through a DSL
* Improved exception message

See gh-21178

Co-authored-by: Brian Clozel <[email protected]>
sbrannen added a commit that referenced this issue Mar 15, 2024
@soc
Copy link

soc commented Aug 18, 2024

are there any workarounds to use assertj or even an alternative to mockmvc that supports it?

You might be happy with the MockMvcWebTestClient variant of the WebTestClient. It does not use AssertJ, but it does have a fluent API and supports MockMvc under the hood.

... which drags org.reactivestreams:reactive-streams¹ in instead of Hamcrest.

So all this effort, and we kinda ended up where we started?!

EDIT: io.projectreactor as well (java.lang.NoClassDefFoundError: reactor/core/scheduler/Schedulers)


¹ new MockMvcHttpConnector fails with java.lang.NoClassDefFoundError: org/reactivestreams/Publisher

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: test Issues in the test module type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests