Cant inject Spring dependencies to both Spock and Spring at same time - spring

I'm having troubles with setting up my tests. I'm using latest version of SpringBoot and Spock Framework. First of all, I'm not configuring my beans "traditional" way. All my classes in package except Facade are package-scoped. I'm not using #Component, #Service, etc.
The only one class Im injecting is Repository. Let me show you my Configuration class
#Configuration
class SurveyConfiguration {
#Bean
SurveyFacade surveyFacade(SurveyRepository surveyRepository) {
ConversionUtils conversionUtils = new ConversionUtils();
SurveyValidator surveyValidator = new SurveyValidator();
SurveyCreator surveyCreator = new SurveyCreator(surveyRepository, conversionUtils, surveyValidator);
return new SurveyFacade(surveyCreator);
}
}
It works fine, I'v tested all scenarios manually (sending POST to certain endpoint). Let me show you example method from SurveyCreator class I want to test.
SurveyDTO createSurvey(final SurveyDTO surveyDTO) throws ValidationException, PersistenceException {
Survey survey = conversionUtils.surveyToEntity(surveyDTO);
surveyValidator.validate(survey);
Optional<Survey> savedInstance = Optional.ofNullable(surveyRepository.save(survey)); //Will throw NullPtr
return savedInstance.map(conversionUtils::surveyToDTO)
.orElseThrow(PersistenceException::new);
}
Like I said, during runtime it works fine. So lets move on to tests
#SpringBootTest
class SurveyFacadeTest extends Specification {
#Autowired
private SurveyRepository surveyRepository
private SurveyFacade surveyFacade = new SurveyConfiguration().surveyFacade(this.surveyRepository)
def "should inject beans"() {
expect:
surveyRepository != null
surveyFacade != null
}
def "should create survey and return id"() {
given:
Long id
when:
id = surveyFacade.createSurvey(SampleSurveys.validSurvey())
then:
id != surveyFacade
}
}
First test passes, so I understand I got everything ok for tests. But, I'm getting NullPointer in my java code in method I posted above. Looks like SurveyRepository isnt injected into java code during tests, because thats the one that causes this exception... Any ideas how to work around that, to have my Repository injected in both Spring application and Spock tests?

If there are no reasons against, I recommend you to run the test on the "underlying bean" (and not a manually created instance):
#Autowired
private SurveyFacade surveyFacade;

Related

Spring Boot / Junit5+Mockito: Mock's mockitoInterceptor gets replaced during test?

I have two service classes (there are more, of course, but those two are relevant here), which are in use during an integration test.
For test, I set up a mock (ConfigurationService) and stub two methods:
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
#SpringBootTest
#TestPropertySource(properties =
"spring.main.allow-bean-definition-overriding=true")
public class VehicleAuditExecutionServiceIT {
#MockBean
private ConfigurationService configurationServiceMock;
#Autowired
private VehicleAuditExecutionService vehicleAuditExecutionService;
#Test
void testExecuteVehicleAudits() throws IOException {
// quite some DB operations for the test setup here
AuditDurationConfigDTO auditDurationConfigDTO = new AuditDurationConfigDTO();
auditDurationConfigDTO.setMaxDuration(deMaxDuration);
Map<String, ScheduledAuditConfigDTO> map = new HashMap<>();
map.put(country, ScheduledAuditConfigDTO.builder()
.groupCalculationEnabled(false)
.build());
when(configurationServiceMock.getAuditDurationConfig(country)).thenReturn(auditDurationConfigDTO);
when(configurationServiceMock.getScheduledAuditConfigurationForCountry(country)).thenReturn(ScheduledAuditConfigDTO.builder()
.groupCalculationEnabled(false)
.sameAuditDurationAllDealersSameGroupId(false)
.build());
vehicleAuditExecutionService.executeVehicleAudits(startDate, country);
verify(publishAuditInterfaceMock).pushExecutions(country, dealerDe1ExportAuditDtoList, auditCategory);
}
#ComponentScan(basePackages = "com.application")
#SpringBootApplication
#PropertySource("classpath:application-test.yml")
static class TestConfiguration {}
}
After the setup, the stubbings are available:
During the test's execution, vehicleAuditExecutionService.executeVehicleAudits(startDate, country) calls the AuditPreparationService, which in turn uses the configurationServiceMock (using #Autowired constructor injection). As expected, the calls gets matched and result set up is returned.
Later, the execution returns to vehicleAuditExecutionService.executeVehicleAudits(startDate, country), where it calls the configurationServiceMock (#Autowired constructor injection, as well) again. But here, the mock's configuration has been changed: the mock's attribute mockitoInterceptor gets replaced by some other instance.
Result: the stubbing is gone and the call returns null - leading to a NPE.
The screenshots were taken using org.springframework.boot:spring-boot-starter-parent:2.7.6, but I've tried that with multiple Spring Boot versions:
2.7.0
2.6.14
2.5.14
2.4.13
2.3.12.RELEASE
Each version has this issue - but I've never seen it in any other test. So I guess, there's something wrong with my test setup - but I cannot spot it.
Any idea, why this is happening?
Thanks a lot - please do not hesitate to ask for any further information, if needed for analysis.
kniffte

spock, mock a method response in a spring bean

I have an integration test written in groovy (spock) in spring boot application. One of the application beans is called Validator it has the follwoing method:
public void validateIssueDates(final List<Timestamp> issueDates) {
issueDates.forEach(issueDate -> {
final Timestamp now = Timestamp.valueOf(LocalDateTime.now());
if (issueDate.before(now)) {
throw new IllegalArgumentException("Issue date is before current date");
}
});
}
In the Validator class there are other methods. In my spock integration test I would like to mock response for that particular method only. In the following way:
Validator.validateIssueDates(_) >> null
I want other validations to take place, but not this one. Bascially I want to achieve this but with spock. I would like to eliminate the validateIssueDates() method from being executed
solution using Spock
It's done using [#SpringSpy][2].
First we annotate field with a spring bean we want to wrap in spy object. For example:
#SpringSpy
private CarValidator carValidator;
then in our test, in then part we define how we want to override method from a a bean/spy:
then:
3 * carValidator.validateIssueDates(_) >> null
Solution using Mockito (as an additional approach, it's not related to spock solution)
I have got that pretty easy using spy in Mockito. Despite many trials (and errors) with spock's spy, It just doesn't want to work. If I get that, I post it here. For now, I can only share Mockito solution:
#Profile("test")
#Configuration
public class BeanConfig {
#Bean
#Primary
public CarValidator getCarValidatorSpy(CarValidator validator) {
CarValidator carValidatorSpy = Mockito.spy(validator);
Mockito.doNothing().when(carValidatorSpy).validateIssueDates(Mockito.any(CarDto.class));
return carValidatorSpy;
}
}
That's all. Seems fairly straightforward.

What is the best way to test that a spring application context fails to start?

I use the spring-boot-starter-web and spring-boot-starter-test.
Let's say I have a class for binding configuration properties:
#ConfigurationProperties(prefix = "dummy")
public class DummyProperties {
#URL
private String url;
// getter, setter ...
}
Now I want to test that my bean validation is correct. The context should fail to start (with a specfic error message) if the property dummy.value is not set or if it contains an invalid URL. The context should start if the property contains a valid URL. (The test would show that #NotNull is missing.)
A test class would look like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApplication.class)
#IntegrationTest({ "dummy.url=123:456" })
public class InvalidUrlTest {
// my test code
}
This test would fail because the provided property is invalid. What would be the best way to tell Spring/JUnit: "yep, this error is expected". In plain JUnit tests I would use the ExpectedException.
The best way to test Spring application context is to use ApplicationContextRunner
It is described in Spring Boot Reference Documentation:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html#boot-features-test-autoconfig
And there is a quick guide about it:
https://www.baeldung.com/spring-boot-context-runner
Sample usage
private static final String POSITIVE_CASE_CONFIG_FILE =
"classpath:some/path/positive-case-config.yml";
private static final String NEGATIVE_CASE_CONFIG_FILE =
"classpath:some/path/negative-case-config.yml";
#Test
void positiveTest() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withInitializer(new ConfigDataApplicationContextInitializer())//1
.withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.DEBUG))//2
.withUserConfiguration(MockBeansTestConfiguration.class)//3
.withPropertyValues("spring.config.location=" + POSITIVE_CASE_CONFIG_FILE)//4
.withConfiguration(AutoConfigurations.of(BookService.class));//5
contextRunner
.run((context) -> {
Assertions.assertThat(context).hasNotFailed();//6
});
}
#Test
void negativeTest() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withInitializer(new ConfigDataApplicationContextInitializer())//1
.withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.DEBUG))//2
.withUserConfiguration(MockBeansTestConfiguration.class)//3
.withPropertyValues("spring.config.location=" + NEGATIVE_CASE_CONFIG_FILE)//4
.withConfiguration(AutoConfigurations.of(BookService.class));//5
contextRunner
.run((context) -> {
assertThat(context)
.hasFailed();
assertThat(context.getStartupFailure())
.isNotNull();
assertThat(context.getStartupFailure().getMessage())
.contains("Some exception message");
assertThat(extractFailureCauseMessages(context))
.contains("Cause exception message");
});
}
private List<String> extractFailureCauseMessages(AssertableApplicationContext context) {
var failureCauseMessages = new ArrayList<String>();
var currentCause = context.getStartupFailure().getCause();
while (!Objects.isNull(currentCause)) {//7
failureCauseMessages.add(currentCause.getMessage());
currentCause = currentCause.getCause();
}
return failureCauseMessages;
}
Explanation with examples of similar definitions from Junit5 Spring Boot Test Annotations:
Triggers loading of config files like application.properties or application.yml
Logs ConditionEvaluationReport using given log level when application context fails
Provides class that specifies mock beans, ie. we have #Autowired BookRepository in our BookService and we provide mock BookRepository in MockBeansTestConfiguration. Similar to #Import({MockBeansTestConfiguration.class}) in test class and #TestConfiguration in class with mock beans in normal Junit5 Spring Boot Test
Equivalent of #TestPropertySource(properties = { "spring.config.location=" + POSITIVE_CASE_CONFIG_FILE})
Triggers spring auto configuration for given class, not direct equivalent, but it is similar to using #ContextConfiguration(classes = {BookService.class}) or #SpringBootTest(classes = {BookService.class}) together with #Import({BookService.class}) in normal test
Assertions.class from AssertJ library, there should be static import for Assertions.assertThat, but I wanted to show where this method is from
There should be static import for Objects.isNull, but I wanted to show where this method is from
MockBeansTestConfiguration class:
#TestConfiguration
public class MockBeansTestConfiguration {
private static final Book SAMPLE_BOOK = Book.of(1L, "Stanisław Lem", "Solaris", "978-3-16-148410-0");
#Bean
public BookRepository mockBookRepository() {
var bookRepository = Mockito.mock(BookRepository.class);//1
Mockito.when(bookRepository.findByIsbn(SAMPLE_BOOK.getIsbn()))//2
.thenReturn(SAMPLE_BOOK);
return bookRepository;
}
}
Remarks:
1,2. There should be static import, but I wanted to show where this method is from
Why is that an integration test to begin with? Why are you starting a full blown Spring Boot app for that?
This looks like unit testing to me. That being said, you have several options:
Don't add #IntegrationTest and Spring Boot will not start a web server to begin with (use #PropertySource to pass value to your test but it feels wrong to pass an invalid value to your whole test class)
You can use spring.main.web-environment=false to disable the web server (but that's silly given the point above)
Write a unit test that process that DummyProperties of yours. You don't even need to start a Spring Boot application for that. Look at our own test suite
I'd definitely go with the last one. Maybe you have a good reason to have an integration test for that?
I think the easiest way is:
public class InvalidUrlTest {
#Rule
public DisableOnDebug testTimeout = new DisableOnDebug(new Timeout(5, TimeUnit.SECONDS));
#Rule
public ExpectedException expected = ExpectedException.none();
#Test
public void shouldFailOnStartIfUrlInvalid() {
// configure ExpectedException
expected.expect(...
MyApplication.main("--dummy.url=123:456");
}
// other cases
}

How to test Spring's declarative caching support on Spring Data repositories?

I have developed a Spring Data repository, MemberRepository interface, that extends org.springframework.data.jpa.repository.JpaRepository. MemberRepository has a method:
#Cacheable(CacheConfiguration.DATABASE_CACHE_NAME)
Member findByEmail(String email);
The result is cached by Spring cache abstraction (backed by a ConcurrentMapCache).
The issue I have is that I want to write an integration test (against hsqldb) that asserts that the result is retrieved from db the first time and from cache the second time.
I initially thought of mocking the jpa infrastructure (entity manager, etc.) and somehow assert that the entity manager is not called the second time but it seems too hard/cumbersome (see https://stackoverflow.com/a/23442457/536299).
Can someone then please provide advice as to how to test the caching behavior of a Spring Data Repository method annotated with #Cacheable?
If you want to test a technical aspect like caching, don't use a database at all. It's important to understand what you'd like to test here. You want to make sure the method invocation is avoided for the invocation with the very same arguments. The repository fronting a database is a completely orthogonal aspect to this topic.
Here's what I'd recommend:
Set up an integration test that configures declarative caching (or imports the necessary bit's and pieces from your production configuration.
Configure a mock instance of your repository.
Write a test case to set up the expected behavior of the mock, invoke the methods and verify the output accordingly.
Sample
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class CachingIntegrationTest {
// Your repository interface
interface MyRepo extends Repository<Object, Long> {
#Cacheable("sample")
Object findByEmail(String email);
}
#Configuration
#EnableCaching
static class Config {
// Simulating your caching configuration
#Bean
CacheManager cacheManager() {
return new ConcurrentMapCacheManager("sample");
}
// A repository mock instead of the real proxy
#Bean
MyRepo myRepo() {
return Mockito.mock(MyRepo.class);
}
}
#Autowired CacheManager manager;
#Autowired MyRepo repo;
#Test
public void methodInvocationShouldBeCached() {
Object first = new Object();
Object second = new Object();
// Set up the mock to return *different* objects for the first and second call
Mockito.when(repo.findByEmail(Mockito.any(String.class))).thenReturn(first, second);
// First invocation returns object returned by the method
Object result = repo.findByEmail("foo");
assertThat(result, is(first));
// Second invocation should return cached value, *not* second (as set up above)
result = repo.findByEmail("foo");
assertThat(result, is(first));
// Verify repository method was invoked once
Mockito.verify(repo, Mockito.times(1)).findByEmail("foo");
assertThat(manager.getCache("sample").get("foo"), is(notNullValue()));
// Third invocation with different key is triggers the second invocation of the repo method
result = repo.findByEmail("bar");
assertThat(result, is(second));
}
}
As you can see, we do a bit of over-testing here:
The most relevant check, I think is that the second call returns the first object. That's what the caching is all about. The first two calls with the same key return the same object, whereas the third call with a different key results in the second actual invocation on the repository.
We strengthen the test case by checking that the cache actually has a value for the first key. One could even extend that to check for the actual value. On the other hand, I also think it's fine to avoid doing that as you tend to test more of the internals of the mechanism rather than the application level behavior.
Key take-aways
You don't need any infrastructure to be in place to test container behavior.
Setting a test case up is easy and straight forward.
Well-designed components let you write simple test cases and require less integration leg work for testing.
I tried testing the cache behavior in my app using Oliver's example. In my case my cache is set at the service layer and I want to verify that my repo is being called the right number of times. I'm using spock mocks instead of mockito. I spent some time trying to figure out why my tests are failing, until I realized that tests running first are populating the cache and effecting the other tests. After clearing the cache for every test they started behaving as expected.
Here's what I ended up with:
#ContextConfiguration
class FooBarServiceCacheTest extends Specification {
#TestConfiguration
#EnableCaching
static class Config {
def mockFactory = new DetachedMockFactory()
def fooBarRepository = mockFactory.Mock(FooBarRepository)
#Bean
CacheManager cacheManager() {
new ConcurrentMapCacheManager(FOOBARS)
}
#Bean
FooBarRepository fooBarRepository() {
fooBarRepository
}
#Bean
FooBarService getFooBarService() {
new FooBarService(fooBarRepository)
}
}
#Autowired
#Subject
FooBarService fooBarService
#Autowired
FooBarRepository fooBarRepository
#Autowired
CacheManager cacheManager
def "setup"(){
// we want to start each test with an new cache
cacheManager.getCache(FOOBARS).clear()
}
def "should return cached foobars "() {
given:
final foobars = [new FooBar(), new FooBar()]
when:
fooBarService.getFooBars()
fooBarService.getFooBars()
final fooBars = fooBarService.getFooBars()
then:
1 * fooBarRepository.findAll() >> foobars
}
def "should return new foobars after clearing cache"() {
given:
final foobars = [new FooBar(), new FooBar()]
when:
fooBarService.getFooBars()
fooBarService.clearCache()
final fooBars = fooBarService.getFooBars()
then:
2 * fooBarRepository.findAll() >> foobars
}
}

Unit testing with Mockito

I am writing unit tests for service layer in my spring application.
Here is my service class
#Service
public class StubRequestService implements RequestService {
#Autowired
private RequestDao requestDao;
#Transactional(propagation = Propagation.REQUIRED, readOnly = true)
#Override
public Request getRequest(Long RequestId) {
Request dataRequest = requestDao.find(requestId);
return dataRequest;
}
}
Here is my test class
#RunWith(MockitoJUnitRunner.class)
#ContextConfiguration(locations = { "/META-INF/spring/applicationContext.xml" })
public class StubRequestServiceTest {
#Mock
public RequestDao requestDao;
StubRequestService stubRequestService; // How can we Autowire this ?
#org.junit.Before
public void init() {
stubRequestService = new StubRequestService(); // to avoid this
stubRequestService.setRequestDao(dataRequestDao);
// Is it necessary to explicitly set all autowired elements ?
// If I comment/remove above setter then I get nullPointerException
}
#Test
public void testGetRequest() {
Request request = new Request();
request.setPatientCnt("3");
when(requestDao.find(anyLong())).thenReturn(request);
assertEquals(stubRequestService.getRequest(1234L).getPatientCnt(),3);
}
}
Its working fine but I have few questions
How can we Autowire service class in test ? I am using constructor in init() method to create service object.
Do we have to set all Autowire element for service class ? For ex StubRequestService have autowired RequestDao which I need to set explicitly before calling test method otherwise it giveds nullPointerException as requestDao is null in StubRequestService.getRequest method.
Which are the good practices to follow while unit testing Spring service layer ? (If I am doing anything wrong).
Your test is fine. It doesn't even have to have the #ContextConfiguration annotation.
The whole point of dependency injection frameworks like Spring is to be able to unit test services by simply instantiating them, setting mock dependencies, and then call their methods.
You're doing it correctly. You don't need to have a Spring context for such unit tests. That's why they're called unit tests: they test it in isolation of all their actual dependencies, Spring included.
Side note: assuming you're using JUnit, the arguments of the assertXxx method should be swapped. The expected value comes before the actual value. It becomes important when the assertion fails and you have a message like "expecting 6 but was 3" rather than "expecting 3 but was 6".
If you really feel that it will make your tests easier to understand - you can initialize a spring context and fetch all of the objects from there. However, usually it will require creating a separate spring configuration XML file specifically for tests therefore I would not recommend it.
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("testApplicationContext.xml");
stubRequestService = (RequestService)applicationContext.getBean("myRequestServiceBean");
(and 3) Basically, I prefer testing each component of my application in total isolation from eachother and that's why I do not recommend what I described in [1].
What that means, is you take a separate logical slice of your application and test only it, while fully mocking up everything it tries to access.
Let's say you have three classes:
//Fetches stuff from some webservice and converts to your app domain POJOs
class DataAccessLayer {
public void setWebservice(Webservice ws) {...};
public MyObject getMyObject() {...};
}
//Formats the domain POJOs and sends them to some kind of outputstream or stuff.
class ViewLayer {
public void setOutputStream(OutputStream os) {...};
public void viewMyObject(MyObject mo) {...};
}
//Main entry point of our MyObject fetch-process-display workflow
class Controller {
public void setDataAccessLayer(DataAccessLayer dal) {...};
public void setViewLayer(ViewLayer vl) {...};
public void showMyObject() {
MyObject mo = dal.getMyObject();
...some processing here maybe...
vl.viewMyObject(mo);
}
}
Now, what tests can we write here?
Test if DataAccessLayer properly converts the object from mocked up WS to our domain object.
Test if ViewLayer properly formats the object given to him and writes it to mocked up output stream.
Test if Controller takes an object from mocked up DataAccessLayer processes it properly and sends it to mocked up ViewLayer.
Or You can use springockito
https://bitbucket.org/kubek2k/springockito/wiki/Home, it will make your tests cleaner

Resources