Override bean in Spring Boot 2.1 slice test with nested TestConfiguration - java

I just migrated an application from Spring Boot 1.x to 2.1. One of my test is failing due to a change to bean overriding default
I tried to set spring.main.allow-bean-definition-overriding to true but it's not working.
You can reproduce the issue with the following classes:
#Configuration
public class ClockConfig {
#Bean
public Clock clock() {
return Clock.systemUTC();
}
}
#Service
public class MyService {
private final Clock clock;
public MyService(Clock clock) {
this.clock = clock;
}
public Instant now() {
return clock.instant();
}
}
#RestController
public class MyResource {
private final MyService myService;
public MyResource(MyService myService) {
this.myService = myService;
}
#GetMapping
public ResponseEntity<Instant> now() {
return ResponseEntity.ok(myService.now());
}
}
The failing test. The clock() method is never called with Spring Boot 2.1 whereas it was with Spring Boot 1.5 or Spring Boot 2.0.
#RunWith(SpringRunner.class)
#WebMvcTest(MyResource.class)
#ContextConfiguration(classes = MyService.class)
public class ResourceTest {
#Autowired
private MockMvc mvc;
#Test
public void test() {
}
#TestConfiguration
static class TestConfig {
#Bean
public Clock clock() {
return Clock.fixed(Instant.MIN, ZoneId.of("Z"));
}
}
}

Try to modify ContextConfiguration annotation. It should be: #ContextConfiguration(classes = {MyService.class,ClockConfig.class}) .
You explicitly specified which config you would like to import in your test with #ContextConfiguration annotation so your #TestConfiguration is not loaded at all. If you exclude your #ContextConfiguration it can work. Since you removed configuration of MyService, you have to provide MyService bean in your test config. Try this:
#RunWith(SpringRunner.class)
#WebMvcTest(MyResource.class)
public class DemoApplicationTests {
#Autowired
private MockMvc mvc;
#Test
public void test() {
}
#TestConfiguration
static class TestConfig {
#Bean
public Clock clock() {
return Clock.fixed(Instant.MIN, ZoneId.of("Z"));
}
#Bean
public MyService service() {
return new MyService(clock());
}
}
}

Related

Overriding spring #Configuration in an integration test

I have a spring boot configuration class like this:
#Configuration
public class ClockConfiguration {
#Bean
public Clock getSystemClock() {
return Clock.systemUTC();
}
}
and I have some integration tests like this:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
}
and tests like this:
public class MiscTests extends AbstractIntegrationTest{
#Test
public void CreateSomethingThatOnlyWorksInThe Morning_ExpectCorrectResponse() {
}
I want to be able to offset the clock bean to run some tests at different times on the day. How do I do this?
NOTE: I see several stack overflow answers similar to this, but I can't get them to work.
Based on other responses, it appears the solution should be something like:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
#Configuration
class MyTestConfiguration {
#Bean
public Clock getSystemClock() {
Clock realClock = Clock.systemDefaultZone();
return Clock.offset(realClock, Duration.ofHours(9));
}
}
}
But nothing happens there. Do I need to #Import something? do I need to #Autowired something?
Thanks!
As you are using Spring Boot you can take advantage of the #MockBean annotation:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
#MockBean
private Clock clockMock;
}
Then you can stub public methods of that bean an each of the tests accordingly and uniquely:
#Test
public void CreateSomethingThatOnlyWorksInThe Morning_ExpectCorrectResponse() {
when(clockMock.getTime()).thenReturn(..);
}
As per javadoc of #MockBean:
Any existing single bean of the same type defined in the context will
be replaced by the mock.
it is the #TestConfiguration annotation that you need https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/TestConfiguration.html
#RunWith(SpringRunner.class)
public class ClockServiceImplIntegrationTest {
#TestConfiguration
static class TestOverridingClockServiceConfiguration {
#Bean
public ClockService clockService() {
return new ClockServiceImpl();
}
}
#Autowired
private ClockService clockService;
#MockBean
private ClockRepository clockRepository;
// write test cases here
}
In the case you have existing configuration you c

Full validation test in Spring Boot, injection failing

Hello everyone I wanted to tested the full validation of a Request in my Spring Boot application I mean no testing one validator at a time but all of them on the target object)
First I have my object :
public class UserCreationRequest {
#JsonProperty("profileId")
#NotNull
#ValidProfile
private Integer profileId;
}
Then my Validator (#ValidProfile):
#Component
public class ProfileValidator implements ConstraintValidator<ValidProfile, Integer> {
#Autowired
private IProfileService profileService;
#Autowired
private IUserRestService userRestService;
#Override
public void initialize(ValidProfile constraintAnnotation) {
}
#Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
RestUser restUser = userRestService.getRestUser();
ProfileEntity profileEntity = profileService.getProfile(value, restUser.getAccountId());
return profileEntity != null;
}
}
Now I write my unit test :
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {ValidationTestConfiguration.class})
public class UserCreationRequestValidationTest {
private static LocalValidatorFactoryBean localValidatorFactory;
#Autowired
private IUserService userService;
#Autowired
private IProfileService profileService;
#Autowired
private IUserRestService restService;
#BeforeClass
public static void createValidator() {
localValidatorFactory = new LocalValidatorFactoryBean();
localValidatorFactory.setProviderClass(HibernateValidator.class);
localValidatorFactory.afterPropertiesSet();
}
#AfterClass
public static void close() {
localValidatorFactory.close();
}
#Test
public void validateUserCreationRequestStringfields() {
UserCreationRequest userCreationRequest = new UserCreationRequest();
/* Here fill test object*/
when(userService.getUser(any(Integer.class), any(Integer.class))).thenReturn(new UserEntity());
when(profileService.getProfile(any(Integer.class), any(Integer.class))).thenReturn(new ProfileEntity());
when(restService.getRestUser()).thenReturn(new RestUser());
Set<ConstraintViolation<UserCreationRequest>> violations
= localValidatorFactory.validate(userCreationRequest);
assertEquals(violations.size(), 8);
}
}
and my TestConfiguration is like that :
#Configuration
public class ValidationTestConfiguration {
#Bean
#Primary
public IProfileService profileService() {
return Mockito.mock(IProfileService.class);
}
#Bean
#Primary
public IUserRestService userRestService() { return Mockito.mock(IUserRestService.class); }
}
On execution I can see that in the test itself the injection works :
restService is mapped to "Mock for IUserRestService"
But in my validator it is not injected, userRestService is null.
Same thing for ProfileService
I tried several things seen here, nothing works (code is running, only test conf is failing)
This is because you do not produce the Validator bean so it can be injected.
As you manually instantiate the LocalValidatorFactoryBean, it cannot access to the spring DI defined for this test.
You should produce instead a bean for the Validator, or even reference an existing spring configuration to do so.

Beans are not Autowired during tests (java.lang.NullPointerException)

my application normally works fine, but when I run tests, or build application by maven, application is shutting down due tests with errors java.lang.NullPointerException. I debugged it and find out my that my beans in service layer are not Autowired and they are null.
Here is my class with tests:
public class CompanyServiceSimpleTest {
private CompanyService companyService;
#Before
public void setUp() {
companyService = new CompanyServiceImpl();
}
// Here is sample test
#Test
public void testNumberOfCompanies() {
Assert.assertEquals(2, companyService.findAll().size());
}
}
companyService is initialized, but beans in it not. Here is CompanyServiceImpl:
#Service
public class CompanyServiceImpl implements CompanyService {
#Autowired
private CompanyRepository companyRepository; // is null
#Autowired
private NotificationService notificationService; // is null
#Override
public List<CompanyDto> findAll() {
List<CompanyEntity> entities = companyRepository.find(0, Integer.MAX_VALUE);
return entities.stream().map(Translations.COMPANY_DOMAIN_TO_DTO).collect(Collectors.toList());
}
// ... some other functions
}
So when is called companyRepository.find() applications crashes. Here is repository class:
#Repository
#Profile("inMemory")
public class CompanyInMemoryRepository implements CompanyRepository {
private final List<CompanyEntity> DATA = new ArrayList<>();
private AtomicLong idGenerator = new AtomicLong(3);
#Override
public List<CompanyEntity> find(int offset, int limit) {
return DATA.subList(offset, Math.min(offset+limit, DATA.size()));
}
// ... some others functions
}
I have set up profile for that service but I had that VM options in Idea:
-Dspring.profiles.active=develpment,inMemory
So it should works.
To make autowiring work it has to be a Spring integration test. You have to anotate your test class with:
#RunWith(SpringJUnit4ClassRunner.class) and #ContextConfiguration(classes = {MyApplicationConfig.class})
If it is a Spring Boot app e.g.:
#RunWith(SpringJUnit4ClassRunner.class) and #SpringBootTest(classes = {MyApp.class, MyApplicationConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
More on this topic: http://www.baeldung.com/integration-testing-in-spring and http://www.baeldung.com/spring-boot-testing
You are not configuring Spring in your TestClasses, so it's impossible to inject anything...
Try configurin your class with
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "path to your config xml" })
A little example:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:config/applicationContext.xml"})
public class MyTest {
#Autowired
private MyClass myInjectedClass;
#Test
public void someTest() {
assertNotNull(myInjectedClass);
}
}

How to create a bean of repository class

I'm doing unit test using spring mvc test framework.
The following is my source code:
com.exmple.main
MyController.java
#Controller
public class MyController {
#Autowired
private MyService myService;
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public Map<Object, Object> myControllerFunction(#RequestBody final Object jsonRequest) {
/* do something */
return response;
}
}
MyRepository.java
#Repository
public interface MyRepository extends JpaRepository<My, String> {
#Query(value="select * from my d where (d.start_date<to_date(:date,'YYYY/DD/MM')) and (d.end_date>to_date(:date,'YYYY/DD/MM'))", nativeQuery=true)
List<My> findByDate(#Param("date") String date);
}
MyService.java
public interface MyService {
List<My> findByDate(String date);
}
MyServiceImpl.java
#Service
public class MyServiceImpl implements MyService {
#Autowired
MyRepository destRepo;
#Override
public List<My> findByDate(String date) {
List<My> listDest = destRepo.findByDate(date);
return listDest;
}
}
com.example.test
MyControllerTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes={TestConfig.class})
#WebAppConfiguration
public class MyControllerTest {
private MockMvc mockMvc;
#Autowired
MyService myService;
#Autowired
protected WebApplicationContext webApplicationContext;
#Before
public void setup() throws Exception {
// this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void listAllMy() throws Exception {
}
}
TestConfig.java
#Configuration
public class TestConfig {
#Bean
public MyService myService() {
// set properties, etc.
return new MyServiceImpl();
}
}
When I run test, the following error is displayed
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException
I know the exception occurred because MyService didn't find any bean of MyRepository.
But I don't know how to create a bean of repository.
Please teach me how to create a bean of repository class using Java (not xml).
You need to enable the JPA repositories in your config class, specify the package that contains the repositories as below
#Configuration
#EnableJpaRepositories(basePackages = {
"com.example.repository"
})
public class TestConfig {
#Bean
public MyService myService() {
// set properties, etc.
return new DestinationServiceImpl();
}
}
Edit: looks like you haven't defined entityManager, and dataSource. Refer to a tutorial here and also answer to similar question here

Using autowired dependencies with certain mock dependency in Spring4

I have a rest resource for signup and login. both in a controller class. the controller class has a dependency to a service class with the business logic. the service class has further dependencies. cause i use an embedded db for testing, i want to use the real dependencies of my app instead to mock them with something like #injectmock #mock. there is only one certain dependency i have to mock. its the dependency for sending emails after a signup process. how to write test cases with #autowired function and one certain mock dependency for email notification?
#Controller
public class AccountCommandsController {
#Autowired
private LogoutService service;
#RequestMapping(value = "/rest/login", method = RequestMethod.POST)
public ResponseEntity login(#RequestBody Account account) {
AccountLoginEvent accountLoginEvent = service.loginAccount(new RequestAccountLoginEvent(account.getEmailAddress(), account.getPassword()));
if (accountLoginEvent.isLoginGranted()) {
return new ResponseEntity(HttpStatus.ACCEPTED);
} else {
return new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
}
#RequestMapping(value = "/rest/signup", method = RequestMethod.POST)
public ResponseEntity signup(#RequestBody Account account) {
AccountSignupEvent signedupEvent = service.signupAccount(new RequestAccountSignupEvent(account.getEmailAddress(), account.getPassword()));
if (signedupEvent.isSignupSuccess()) {
return new ResponseEntity(HttpStatus.ACCEPTED);
} else if (signedupEvent.isDuplicateEmailAddress()) {
return new ResponseEntity(HttpStatus.CONFLICT);
} else if (signedupEvent.isNoSignupMailSent()) {
return new ResponseEntity(HttpStatus.SERVICE_UNAVAILABLE);
} else {
return new ResponseEntity(HttpStatus.FORBIDDEN);
}
}
}
#Service
public class LogoutService {
#Autowired
private AccountsRepository accountsRepository;
#Autowired
private MailService mailService;
#Autowired
private HashService hashService;
public AccountSignupEvent signupAccount(RequestAccountSignupEvent signupEvent) {
if (accountsRepository.existEmailAddress(signupEvent.getEmailAddress())) {
return AccountSignupEvent.duplicateEmailAddress();
}
Account newAccount = new Account();
newAccount.setCreated(new Date());
newAccount.setModified(new Date());
newAccount.setEmailAddress(signupEvent.getEmailAddress());
newAccount.setPassword(signupEvent.getPassword());
newAccount.setVerificationHash(hashService.getUniqueVerificationHash());
SignupMailEvent mailSentEvent = mailService.sendSignupMail(new RequestSignupMailEvent(newAccount));
if (!mailSentEvent.isMailSent()) {
return AccountSignupEvent.noMailSent();
}
Account persistedAccount = accountsRepository.persist(newAccount);
return AccountSignupEvent.accountCreated(persistedAccount);
}
public AccountLoginEvent loginAccount(RequestAccountLoginEvent loginEvent) {
if (accountsRepository.existLogin(loginEvent.getEmailAddress(), loginEvent.getPassword())) {
return AccountLoginEvent.granted();
}
return AccountLoginEvent.denied();
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestConfiguration.class)
#Transactional
#TransactionConfiguration(defaultRollback = true)
public class LogoutTest {
private MockMvc mockMvc;
#Autowired
private AccountCommandsController controller;
#Before
public void setup() {
mockMvc = standaloneSetup(controller).build();
}
#Test
public void signupNoMail() throws Exception {
doReturn(AccountSignupEvent.noMailSent()).when(service).signupAccount(any(RequestAccountSignupEvent.class));
// when(controller.service.signupAccount(any(RequestAccountSignupEvent.class))).thenReturn(AccountSignupEvent.noMailSent());
mockMvc.perform(post("/rest/signup")
.content(new Gson().toJson(new Account(UUID.randomUUID().toString(), UUID.randomUUID().toString())))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isServiceUnavailable());
}
}
I hope you see the problem. Every dependency works fine instead mailservice. I dont want to use #injectmock and #mock with MockitoAnnotations.initMocks(this); in my test file, because of the neccessary to provide for all dependencies mocks.
if your dependencies are running and you have a configuration class where you have defined the endpoint, you can use ConfigurableApplicationContext class, something like this:
public class test {
private static ConfigurableApplicationContext appContext;
private LogoutService service;
#AfterClass
public static void destroy() {
appContext.close();
}
#Before
public void setup() {
appContext = new AnnotationConfigApplicationContext(YourClassConfig.class);
service = appContext.getBean(LogoutService.class);
}
#Test
public void beansAreCreated() {
assertNotNull(service);
}
}
Or you can re-write your endpoint with a configuration class and you can use WireMock (http://wiremock.org) to emulate your dependency with real data, this should be something like this:
public class test {
#Rule
public WireMockRule wireMockRule = new WireMockRule(15000);
private static ConfigurableApplicationContext appContext;
private LogoutService service;
private static String serviceMockUrl;
#AfterClass
public static void destroy() {
appContext.close();
}
#Before
public void setup() {
serviceMockUrl = "http://localhost:" + wireMockRule.port();
appContext = new AnnotationConfigApplicationContext(TestConfig.class);
stubFor(get(urlEqualTo("urlToRequest")).
willReturn(aResponse().
withStatus(SC_OK).
withBody(createJsonArray("MapWithYourData").
withHeader("Content-Type", "application/json")));
service = appContext.getBean(LogoutService.class);
}
#Test
public void beansAreCreated() {
assertNotNull(service);
}
#Configuration
static class TestConfig {
#Bean
public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertyPlaceholderConfigurer() {{
setProperties(new Properties() {{
setProperty("service.url", serviceMockUrl);
}});
}};
}
}
}
I hope this help you.
What you are trying to do is easily implemented using Spring Profiles.
On way to achieve it is the following:
#Configuration
public class TestConfiguration {
//this is the real mail service
#Bean
public MailService mailService() {
return new MailService(); //or whatever other bean creation logic you are using
}
//whatever else
}
#Configuration
#Profile("mockMail")
public class MockMailServiceConfig {
#Bean
#Primary
public MailService mockMailService() {
return mock(MailService.class);
}
}
Your test class would then look like:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestConfiguration.class)
#Transactional
#TransactionConfiguration(defaultRollback = true)
#ActiveProfiles("mockMail")
public class LogoutTest {
//do your testing
}
Note the use of #Primary in MockMailServiceConfig. I opted for this way since it wouldn't require you to introduce profiles anywhere else if you are not already using them. #Primary tells spring to use that specific bean if multiple candidates are available (in this case there is the real mail service and the mock service)

Categories

Resources