I have this test:
#ExtendWith(SpringExtension.class)
#WebMvcTest(AuthController.class)
#TestPropertySource("classpath:application.properties")
class AuthControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
AuthTokenFilter authTokenFilter;
#MockBean
AuthEntryPointJwt authEntryPointJwt;
#MockBean
JwtUtils jwtUtils;
#Autowired
private ObjectMapper objectMapper;
#MockBean
UserDetailsServiceImpl userDetailsServiceImpl;
#MockBean
AuthenticationManager authenticationManager;
#MockBean
Authentication authentication;
#MockBean
SecurityContext securityContext;
#Test
void test1withEnabledTrue() {
}
#Test
void test2WithEnabledTrue() {
}
#Test
void cannotRegisterUserWhenRegistrationsAreDisabled() throws Exception {
var userToSave = validUserEntity("username", "password");
var savedUser = validUserEntity("username", "a1.b2.c3");
when(userDetailsServiceImpl.post(userToSave)).thenReturn(savedUser);
mvc.perform(post("/api/v1/auth/register/").contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(userToSave))).andExpect(status().isCreated())
.andExpect(jsonPath("$.status", is("registrations are disabled")));
}
private static UsersEntity validUserEntity(String username, String password) {
return UsersEntity.builder().username(username).password(password).build();
}
}
And this is the relevant part in Controller (Class Under Test):
#Value("${app.enableRegistration}")
private Boolean enableRegistration;
private Boolean getEnableRegistration() {
return this.enableRegistration;
}
#PostMapping("/register")
#ResponseStatus(HttpStatus.CREATED)
public Map<String, String> post(#RequestBody UsersDTO usersDTO) {
Map<String, String> map = new LinkedHashMap<>();
if (getEnableRegistration()) {
[...]
map.put("status", "ok - new user created");
return map;
}
map.put("status", "registrations are disabled");
return map;
}
I have this application.properties under src/test/resources and I need to override it, only for my single test named cannotRegisterUserWhenRegistrationsAreDisabled
app.enableRegistration=true
Probably I could use another file "application.properties" and another class test, but I'm looking for a smarter solution.
You can simply configure the inline properties of #TestPropertySource which has a higher precedence than the properties loaded from locations/ value :
#WebMvcTest(AuthController.class)
#TestPropertySource(locations = "classpath:application.properties" ,properties="app.enableRegistration=true" )
class AuthControllerTest {
}
All inline properties specified in the properties will override those specified in the application.properties
I think what you are looking for is the #TestProperty annotation, which was an answer of a question on Stackoverflow here. However this only works on class level, not for one test only.
You probably need to make a new test class and add the tests where it the value needs to be false.
Related
I have a controller class:
public class Controller {
private final IProcessor processor;
public Controller (final ProcessorFactory factory) {
this.processor = factory.getInstance();
}
}
A Factory class to provide the different instances of IProcessor:
#Component
public class ProcessorFactory {
private final Dep1 dep1;
private final Dep2 dep2;
public ProcessorFactory (final Dep1 dep1,
final Dep2 dep2) {
this.dep1= dep1;
this.dep2= dep2;
}
public IProcessor getInstance() {
if (...) {
return new ProcessorA(dep1, dep2);
}
return new ProcessorB(dep1, dep2);
}
}
In my mockito test class where I use Junit5, I am not able to instantiate the IProcessor member and is null:
#WebMvcTest(Controller.class)
public class ControllerTest {
#MockBean
private ProcessorFactory processorFactory ;
#MockBean
private IProcessor processor;
#Autowired
private MockMvc mockMvc;
#Test
public void test1() throws Exception {
when(processor.process(any(Request.class), any(String.class)))
.thenReturn(new BlaBla("Test", "Test"));
String request = ...
this.mockMvc.perform(post("/test/test").contentType(MediaType.APPLICATION_JSON).content(request))
.andDo(print())
.andExpect(status().is2xxSuccessful());
}
}
I am not sure I am using MockBean correctly. Basically I want to mock both the Factory and the Processor.
Since you need to call a mocked method (getInstance()) during Spring context initialization (inside the Controller's constructor), you need to mock the said method in a different way. The mocked bean has to be not only provided as an existing object, but also it should have it's mocked behavior defined.
Addtionally, IProcessor implementations are not configured as Spring beans, so Spring will not inject them - ProcessorFactory calls new explicitly and creates the objects without Spring involvement.
I've created a simple project to reproduce your problem and provide a solution - you can find it here on GitHub if you want to check if the whole thing is working, but here's the most important test snippet (I've simplified the methods a bit):
#WebMvcTest(Controller.class)
class ControllerTest {
private static final IProcessor PROCESSOR = mock(IProcessor.class);
#TestConfiguration
static class InnerConfiguration {
#Bean
ProcessorFactory processorFactory() {
ProcessorFactory processorFactory = mock(ProcessorFactory.class);
when(processorFactory.getInstance())
.thenReturn(PROCESSOR);
return processorFactory;
}
}
#Autowired
private MockMvc mockMvc;
#Test
void test1() throws Exception {
String result = "this is a test";
when(PROCESSOR.process(any()))
.thenReturn(result);
mockMvc.perform(post("/test/test")
.contentType(MediaType.APPLICATION_JSON)
.content("{}"))
.andDo(print())
.andExpect(status().is2xxSuccessful())
.andExpect(content().string(result));
}
}
I try to test my spring app but encounter following problem:
In "normal mode"(mvn spring-boot:run) the app starts as expected and adapterConfig gets set and is NOT NULL. When I start my testclass to test the MVC, adapterConfig does not get set. Spring ignores the whole config class.
test:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = StudentController.class)
public class StudentControllerTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private StudentService service;
#MockBean
private StudentRepository repository;
#Test
public void shouldReturnABC() throws Exception{
MvcResult result = this.mockMvc.perform(get("/students/abc")).andReturn();
}
}
controller:
#RestController
#RequestMapping("/students")
#PermitAll
public class StudentController {
#Autowired
StudentService studentService;
//get
#GetMapping("/abc")
public String abc (){
return "abc";
}
config:
#Configuration
public class SpringBootKeycloakConfigResolver implements KeycloakConfigResolver {
private KeycloakDeployment keycloakDeployment;
private AdapterConfig adapterConfig;
#Autowired
public SpringBootKeycloakConfigResolver(AdapterConfig adapterConfig) {
this.adapterConfig = adapterConfig;
}
#Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
if (keycloakDeployment != null) {
return keycloakDeployment;
}
keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfig);
return keycloakDeployment;
}
}
adapterConfig is null when hitting the test but gets set & created when hitting it the normal way, any idea?
Using #WebMvcTest, the container will inject only components related to Spring MVC (#Controller, #ControllerAdvice, etc.) not the full configuration use #SpringBootTest with #AutoConfigureMockMvc instead.
Spring Boot Javadoc
Keycloak's AutoConfiguration is not included by #WebMvcTest.
You could
Include it manually via #Import(org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration.class)
Or use #SpringBootTest
with spring boot 2.5 i had I had to import KeycloakAutoConfiguration into my test.
#WebMvcTest(value = ApplicationController.class, properties = "spring.profiles.active:test")
#Import(KeycloakAutoConfiguration.class)
public class WebLayerTest {
// ... test code ....
}
I want to configure Spring feign with a configuration class, and I want to make sure that all the #Bean methods are called when Spring configures the feign client for me.
How to test it?
For example, I have:
#FeignClient(
name = "PreAuthSendRequest",
url = "${xxx.services.preauth.send.url}",
configuration = AppFeignConfig.class)
public interface RequestService {
#PostMapping("")
#Headers("Content-Type: application/json")
PreAuthResponse execute(#RequestBody PreAuthRequest preAuthRequest);
}
And AppFeignConfig.java:
#Configuration
#RequiredArgsConstructor
public class AppFeignConfig{
private final HttpClient httpClient;
private final Jackson2ObjectMapperBuilder contextObjectMapperBuilder;
#Bean
public ApacheHttpClient client() {
return new ApacheHttpClient(httpClient);
}
#Bean
public Decoder feignDecoder() {
return new JacksonDecoder((ObjectMapper)contextObjectMapperBuilder.build());
}
#Bean
public Encoder feignEncoder() {
return new JacksonEncoder((ObjectMapper)contextObjectMapperBuilder.build());
}
#Bean
public Retryer retryer() {
return Retryer.NEVER_RETRY;
}
#Bean
public ErrorDecoder errorDecoder() {
return new ServiceResponseErrorDecoder();
}
}
So, how to verify that all #Bean methods are called? I know #MockBean, but what I want to check is config.feignDecoder(), etc., are indeed called.
When I am trying to context.getBean(RequestService.class); and call execute() method, it seems to send a real request and without wiremock, it fails, obviously.
For now I have this:
#SpringBootTest
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
class RequestServiceTest {
#Autowired
private ApplicationContext applicationContext;
#MockBean
private ApacheHttpClient client;
#MockBean
private Decoder feignDecoder;
#MockBean
private Encoder feignEncoder;
#MockBean
private Retryer retryer;
#MockBean
private ErrorDecoder errorDecoder;
#Test
void shouldRetrieveBeansFromApplicationContextToConstructConfigurationInstance() {
AppFeignConfig config = applicationContext.getBean(AppFeignConfig.class);
Assertions.assertEquals(config.feignEncoder(), feignEncoder);
Assertions.assertEquals(config.feignDecoder(), feignDecoder);
Assertions.assertEquals(config.errorDecoder(), errorDecoder);
Assertions.assertEquals(config.client(), client);
Assertions.assertEquals(config.retryer(), retryer);
}
}
I don't know if it is how it should be. If any idea, please comment.
This class is in the top of my tests hierarchy:
#TestPropertySource("/test.properties")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public abstract class ApplicationAbstractTest {
}
And few more test classes:
#WebAppConfiguration
#ActiveProfiles("mysql")
abstract public class AbstractControllerTest extends ApplicationAbstractTest {
protected MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#PostConstruct
private void postConstruct() {
mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.build();
}
}
JsonUserServiceTest:
#ActiveProfiles("json")
public class JsonUserServiceTest extends ApplicationAbstractTest {
#Before
public void setUp() throws Exception {
...
}
}
ContactControllerTest:
public class ContactControllerTest extends AbstractControllerTest {
#Test
public void testGet() throws Exception {
mockMvc.perform(get("/update-" + ID + "-contact")
.with(userAuth(USER)))
// .andExpect(status().isOk())
.andDo(print())
.andExpect(view().name("details"))
.andExpect(forwardedUrl("/WEB-INF/jsp/details.jsp"));
}
}
So, when I run ContactControllerTest along - it is successfull, and print method shows me:
Handler:
Type = com.telecom.web.ContactController
Method = public java.lang.String com.myApp.web.ContactController.details(java.lang.Integer,org.springframework.ui.ModelMap)
But when I run all tests, so JsonUserServiceTest runs first, ContactControllerTest fails. And print shows:
Handler:
Type = null
...
java.lang.AssertionError: No ModelAndView found
What is wrong in configuration? Or how troubleshoot it?
UPD:
at the same time, test like this, allways works fine:
public class UserControllerTest extends AbstractControllerTest {
#Test
public void testRegister() throws Exception {
mockMvc.perform(get("/register"))
.andDo(print())
.andExpect(view().name("profile"))
.andExpect(forwardedUrl("/WEB-INF/jsp/profile.jsp"));
}
}
UPD:
There is controller's method I'm testing:
#GetMapping("/update-{id}-contact")
public String details(#PathVariable Integer id, ModelMap model) {
Integer userId = AuthorizedUser.id();
LOG.info("get contact {} for User {}", id, userId);
Contact contact = service.get(id, userId);
model.addAttribute("contact", contact);
return "details";
}
I also have such bean:
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
UPD: I've tried configure mockMvc in separate class:
#Configuration
public class TestConfig {
#Autowired
private WebApplicationContext webApplicationContext;
#Bean
public MockMvc mockMvc() {
return MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.build();
}
}
And added it here:
#WebAppConfiguration
#ContextConfiguration(classes = {TestConfig.class})
#ActiveProfiles("mysql")
abstract public class AbstractControllerTest extends ApplicationAbstractTest {
but I've received:
java.lang.IllegalStateException: springSecurityFilterChain cannot be
null. Ensure a Bean with the name springSecurityFilterChain
implementing Filter is present or inject the Filter to be used.
The WARN message doesn't cause the test cases to fail. It just says that Entity manager factory is registered twice. This will only be an issue if you cluster your application using the same Entity Manager Factory. For test case run it is not a cause for concern.
The root cause of the testcase failure is in these two lines
.andExpect(view().name("details"))
.andExpect(forwardedUrl("/WEB-INF/jsp/details.jsp"));
Please check if the project has a view named "details" and the forwardded url is "/WEB-INF/jsp/details.jsp"
Update
Could you please try this
#Configuration
public class TestConfig {
#Autowired
private Filter springSecurityFilterChain;
#Autowired
private WebApplicationContext webApplicationContext;
#Bean
public MockMvc mockMvc() {
return MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurityFilterChain)
.build();
}
}
Create a configuration file that will initialize mocking objects for your test cases. And put at all test case classes.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestConfig.class})
It will initialize all your mocking objects only once and cached after that and reused for all test cases.
Or if you don't want to use mocking configuration, you can directly
pass the actual application configuration to ContextConfiguration as
below
For annotation based application configuration (here AppConfig and AppConfig2 are your configuration class)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {AppConfig.class, AppConfig2.class})
For xml based application configuration (here appConfig.xml and appConfig2.xml are your configuration files)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:pathTo/appConfig.xml","classpath:pathTo/appConfig2.xml"})
Reference : JUnit + Spring integration example
I am trying to write a IntegrationFlow test. It goes something like this:
JMS(in) -> (find previous versions in db) -> reduce(in,1...n) -> (to db) -> JMS(out)
So, no suprise: I want to mock the DB calls; they are Dao beans. But, I also want it to pickup other beans through component scan; I will selectively scan all packages except dao.
Create a test config and mock the Daos. No problem
Follow spring boot instructions for testing to get Component scanned beans. No problem
I just want to verify the sequence of steps and the resultant output as the outbound JMS queue would see it. Can someone just help me fill in the blanks?
This CANT be tough! The use of mocks seems to be problematic because plenty of essential fields are final. I am reading everywhere about this and just not coming up with a clear path. I inherited this code BTW
My error:
org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers
Here is my code
#Configuration
#ImportResource("classpath:retry-context.xml")
public class LifecycleConfig {
#Autowired
private MessageProducerSupport inbound;
#Autowired
private MessageHandler outbound;
#Autowired
#Qualifier("reducer")
private GenericTransformer<Collection<ExtendedClaim>,ExtendedClaim> reducer;
#Autowired
#Qualifier("claimIdToPojo")
private GenericTransformer<String,ClaimDomain> toPojo;
#Autowired
#Qualifier("findPreviousVersion")
private GenericTransformer<ExtendedClaim,Collection<ExtendedClaim>> previousVersions;
#Autowired
#Qualifier("saveToDb")
private GenericHandler<ExtendedClaim> toDb;
#Bean
public DirectChannel getChannel() {
return new DirectChannel();
}
#Bean
#Autowired
public StandardIntegrationFlow processClaim() {
return IntegrationFlows.from(inbound).
channel(getChannel()).
transform(previousVersions).
transform(reducer).
handle(ExtendedClaim.class,toDb).
transform(toPojo).
handle(outbound).get();
}
}
Test Config
#Configuration
public class TestConfig extends AbstractClsTest {
#Bean(name = "claimIdToPojo")
public ClaimIdToPojo getClaimIdToPojo() {
return spy(new ClaimIdToPojo());
}
#Bean
public ClaimToId getClaimToIdPojo() {
return spy(new ClaimToId());
}
#Bean(name = "findPreviousVersion")
public FindPreviousVersion getFindPreviousVersion() {
return spy(new FindPreviousVersion());
}
#Bean(name = "reducer")
public Reducer getReducer() {
return spy(new Reducer());
}
#Bean(name = "saveToDb")
public SaveToDb getSaveToDb() {
return spy(new SaveToDb());
}
#Bean
public MessageProducerSupport getInbound() {
MessageProducerSupport mock = mock(MessageProducerSupport.class);
// when(mock.isRunning()).thenReturn(true);
return mock;
}
#Bean
public PaymentDAO getPaymentDao() {
return mock(PaymentDAO.class);
}
#Bean
public ClaimDAO getClaimDao() {
return mock(ClaimDAO.class);
}
#Bean
public MessageHandler getOutbound() {
return new CaptureHandler<ExtendedClaim>();
}
}
Actual test won't load
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestConfig.class, LifecycleConfig.class})
public class ClaimLifecycleApplicationTest extends AbstractClsTest {
#Autowired
private MessageHandler outbound;
#Autowired
#Qualifier("reducer")
private GenericTransformer<Collection<ExtendedClaim>,ExtendedClaim> reducer;
#Autowired
#Qualifier("claimIdToPojo")
private GenericTransformer<String,ClaimDomain> toPojo;
#Autowired
#Qualifier("findPreviousVersion")
private GenericTransformer<ExtendedClaim,Collection<ExtendedClaim>> previousVersions;
#Autowired
#Qualifier("saveToDb")
private GenericHandler<ExtendedClaim> toDb;
#Autowired
private DirectChannel defaultChannel;
#Test
public void testFlow() throws Exception {
ExtendedClaim claim = getClaim();
Message<ExtendedClaim> message = MessageBuilder.withPayload(claim).build();
List<ExtendedClaim> previousClaims = Arrays.asList(claim);
defaultChannel.send(message);
verify(previousVersions).transform(claim);
verify(reducer).transform(previousClaims);
verify(toDb).handle(claim, anyMap());
verify(toPojo).transform(claim.getSubmitterClaimId());
verify(outbound);
}
}
There are a lot of domain-specific object, so I can't test it to reproduce or find some other issue with your code.
But I see that you don't use an #EnableIntegration on your #Configurations classes.