How to use MyBatis mappers in Spring's JUnit tests? - spring

For tests I use:
Spring Test 3.2.3.RELEASE
JUnit 4.12
Mockito 1.10.19
The following test code should save some entity into the database, however, this does not occur:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {ControllerTestConfig.class})
public class ControllerTest {
#Autowired
SomeMapper someMapper;
#Test
public void shouldCreateSomeEntity() {
SomeEntity someEntity = new SomeEntity();
someEntity.setSomeProperty("property");
...
someMapper.createSomeEntity(someEntity);
}
...
}
I use a simulated implementation of the mapper:
#Configuration
public class ControllerTestConfig {
#Bean
public SomeMapper SomeMapper() {
return Mockito.mock(SomeMapper.class);
}
...
}
Because the implementation is simulated, the method call is intercepted in the class org.mockito.internal.creation.cglib.MethodInterceptorFilter.
The mapper is an interface:
public interface SomeMapper {
#Insert("Insert into some_table (id, some_entity_id, type, full_name) values (#{id}, #{someEntityId}, #{type}, #{fullName})")
#SelectKey(statement="select nextval('seqid_static_data');", keyProperty="id", before=true, resultType=long.class)
void createSomeEntity(SomeEntity someEntity);
...
}
Thus, it is not possible to create an instance of this mapper. For example, by this way:
#Bean
public SomeMapper SomeMapper() {
return new SomeMapper();
}
...
How to use MyBatis mappers in Spring's JUnit tests?

Did you try to emulate method call by doAnswer or doThrow, they should works with void methods. For example:
#Test
public void shouldCreateSomeEntity() {
SomeEntity someEntity = new SomeEntity();
someEntity.setSomeProperty("property");
Mockito.doAnswer(invocation -> {
invocation.getArgument(0).setSomeProperty("changed_property")
}).when(someMapper).createSomeEntity(Mockito.eq(someEntity));
someMapper.createSomeEntity(someEntity);
Assert.assertEquals("changed_property", someEntity.getSomeProperty());
}

Related

Spring Boot Injecting Implementations for Prod and Test

I'm new to spring boot and I'm trying to wrap my head around how to make dependency injection work for deployment and testing.
I have a #RestController and a supporting #Service. The service injects another class that is an interface for talking to Kafka. For the Kafka interface I have two implementations: one real and one fake. The real one I want to use in production and the fake in test.
My approach is to use two different configuration for each environment (prod and test).
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
Then in my main application I would like to somehow load AppConfiguration.
#SpringBootApplication
public class DeployerServiceApiApplication {
public static void main(String[] args) {
SpringApplication.run(DeployerServiceApiApplication.class, args);
}
// TODO: somehow load here...
}
And in my test load the fake configuration somehow
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest {
#Autowired private MockMvc mockMvc;
// TODO: somehow load AppTestConfiguration here
#Test
public void testDeployAction() throws Exception {
...
ResultActions resultActions = mockMvc.perform(...);
...
}
}
I've spent the better part of a day trying to figure this out. What I'm trying to accomplish here is fundamental and should be straight forward yet I keep running into issues which makes me wonder if the way I'm thinking about this is all wrong.
Am not sure if i understand your question completely but from description i guess you wish to initialize bean based on environment. Please see below.
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
and then you can pass the "-Dspring.profiles.active=prod" argument while starting you application using java command or you can also specify the profile in your test case like below.
#SpringBootTest
#ActiveProfile("test")
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest
Use spring profiles, you can annotate your test class with #ActiveProfiles("test-kafka") and your test configuration with #Profile("test-kafka").
This is pretty easy task in spring boot world
Rewrite your classes as follows:
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
This will instruct spring boot to load the relevant configuration when the "prod"/"test" specified.
Then you can start your application in production with --spring.profiles.active=prod and in the Test you can write something like this:
#SpringBootTest
#ActiveProfiles("test")
public class DeployerServiceApiApplicationTest {
...
}
If you want to run all the tests with this profile and do not want to write this ActiveProfiles annotation you can create src/test/resources/application.properties and put into it: spring.active.profiles=test

Spring 2 + JUnit 5, share #MockBean for entire test suite

I create a Spring 2.3 application using Spring Data REST, Hibernate, Mysql.
I created my tests, I've around 450 tests splitted in about 70 files. Because the persistence layer leans on a multi tenant approach (single db per tenant) using a Hikari connection pool, I've the need to avoid the pool is initializated for each test file but at the same time I need to use #MockBean because I need to mock up some repositories in the entire Spring test contest.
I create a custom annotation for all test in my suite:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#SpringBootTest
#TestExecutionListeners(value = TestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#Transactional
#ActiveProfiles("test")
public #interface TestConfig {
}
Reading many posts and the doc, I know if I use #MockBean inside a test, the Spring context is reloaded and therefore a new pool connection is created in my case.
My idea is to create a #MockBean and share it with all tests in my suite so the context is not reloaded every time.
I tried several approaches:
#Log4j2
public class TestExecutionListener extends AbstractTestExecutionListener implements Ordered {
#Override
public void beforeTestMethod(TestContext testContext) throws Exception {
try {
TestDbUtils testDbUtils = (TestDbUtils) testContext.getApplicationContext().getBean(TestDbUtils.class);
testDbUtils.truncateDB();
TenantRepository tenantRepository = mock(TenantRepository.class);
testContext.setAttribute("tenantRepository", tenantRepository);
TenantContext.setCurrentTenantId("test");
when(tenantRepository.findByTenantId("test")).thenReturn(testDbUtils.fakeTenant());
} catch (Exception e) {
}
}
#Override
public int getOrder() {
return Integer.MAX_VALUE;
}
}
All my tests are annotated like this:
#TestConfig
#Log4j2
public class InvoiceTests {
#Test
public void test1(){
}
}
Unfortunately my tenantRepository.findByTenantId() is not mocked up. I also tried to create an abstract superclass:
#SpringBootTest
#TestPropertySource(locations = "classpath:application-test.properties")
#TestExecutionListeners(value = TestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#Transactional
#ActiveProfiles("test")
public abstract class AbstractIntegrationTest {
#MockBean
protected TenantRepository tenantRepository;
#MockBean
protected SubscriptionRepository subscriptionRepository;
#Autowired
protected TestDbUtils testDbUtils;
#BeforeAll
public void beforeAll() {
when(tenantRepository.findByTenantId("test")).thenReturn(testDbUtils.fakeTenant());
}
#BeforeEach
public void setup() {
testDbUtils.truncateDB();
TenantContext.setCurrentTenantId("test");
}
}
Even if my tests extended this superclass, during the run all of them were skipped (not sure why).
Is there any way to accomplish what I described?

How to test Spring transactions

I'm working to a project with Spring Boot 2.1.0 and I've the following situation.
I've the following repository
#Repository
public interface ActivityRepository extends PagingAndSortingRepository<Activity, Long> {
#Transactional
#Modifying
#Query("") // Imagine a query
void updateBacklogStatusAge();
#Transactional
#Modifying
#Query("QUERY 2") // Imagine a query
void updateNextStatusAge();
#Transactional
#Modifying
#Query("QUERY 3") // Imagine a query
void updateInProgressStatusAge();
}
and the following component
#Component
public class ColumnAgeJob {
private final ActivityRepository activityRepository;
public ColumnAgeJob(final ActivityRepository pActivityRepository) {
activityRepository = pActivityRepository;
}
#Transactional
public void update() {
activityRepository.updateBacklogStatusAge();
activityRepository.updateNextStatusAge();
activityRepository.updateInProgressStatusAge();
}
}
Now I want to test if the transactional annotation is working.
Basically my goal is to check if a runtimeException raised during the updateInProgressStatusAge() call will cause a rollback of updateNextStatusAge and updateBacklogStatusAge modifications.
How can I do that?
Thank you
You can use Mockito in order to change the behaviour of your service or repository by using #SpyBean or #MockBean.
Unfortunately #SpyBean do not works on JPA repository (https://github.com/spring-projects/spring-boot/issues/7033, this issue is for Spring boot 1.4.1, but I have the same problem with 2.0.3.RELEASE)
As workaround you can create a test configuration to create manually your mock:
#Configuration
public class SpyRepositoryConfiguration {
#Primary
#Bean
public ActivityRepository spyActivityRepository(final ActivityRepository real)
return Mockito.mock(ActivityRepository.class, AdditionalAnswers.delegatesTo(real));
}
}
And in your test:
#Autowired
private ActivityRepository activityRepository;
....
#Test
public void testTransactional() {
Mockito.doThrow(new ConstraintViolationException(Collections.emptySet())).when(activityRepository).updateInProgressStatusAge();
activityRepository.updateBacklogStatusAge();
activityRepository.updateNextStatusAge();
activityRepository.updateInProgressStatusAge();
// verify that rollback happens
}
You can change your method to test your transactional annotation.
#Transactional
public void update() {
activityRepository.updateBacklogStatusAge();
activityRepository.updateNextStatusAge();
throw Exception();
activityRepository.updateInProgressStatusAge();
}
This will simulate your desired scenario.

Precedence of #DatabaseSetup and #BeforeTransaction Spring DBUnit and Spring Transaction

I have a JUnit test where I am using Spring Test DBUnit and Spring declarative transaction management. If I tag a test method with #Transactional (which will utilize the #BeforeTransactional to verify database state) and also with #DatabaseSetup (to set the database state to what I want), which will have precedence? Will the database first be set up and then #BeforeTransactional will check it, or will the check happen after setup? Here's some code:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = PersonServiceConfiguration.class)
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class })
public class PersonServiceTransactionalIT {
#Autowired
private PersonManagementService pms;
#BeforeTransaction
public void beforeTransaction() {
checkDatabaseState();
}
#AfterTransaction
public void afterTransaction() {
checkDatabaseState();
}
#Test
#Transactional
#DatabaseSetup("/test_data_start.xml")
public void testCreateValid() {
Person expected = new Person(6l, "six", "created");
Person actual = pms.save(expected);
assertEquals("Person objects not equal", expected, actual);
}
private void checkDatabaseState() {
List<Person> pl = pms.findAll();
assertEquals("Size of database not as expected", 5, pl.size());
}
}

AOP Spring Before Advice not working

The method DefaultProduitGeneriqueService.valider is not catched by the method traceWhenReturnedValueDoesntExistOrNotNecessary and I don't understand why?
package fr.generali.nova.atp.service.metier.impl;
public class DefaultProduitGeneriqueService extends DefaultService implements IProduitGeneriqueService, IBacAware {
...
#Override
#Traceable(value = ETraceableMessages.VALIDATION_PRODUIT_GENERIQUE, hasReturnedValue=Traceable.HAS_NOT_RETURNS_VALUE)
public void valider(ElementNiveauUn element) {
...
}
...
}
package fr.generali.nova.atp.logging.advisor;
#Aspect
public class TraceableAdvisor {
#Before(value = "execution(* fr.generali.nova.atp.service.metier.impl.*.*(..)) && #annotation(traceable) && args(element)", argNames = "element,traceable")
public void traceWhenReturnedValueDoesntExistOrNotNecessary(ElementNiveauUn element, Traceable traceable) {
...
}
}
Assuming that the service interfaces are in package fr.generali.nova.atp.service.metier.api:
package fr.generali.nova.atp.service.metier.api;
public interface IProduitGeneriqueService {
void valider(ElementNiveauUn element);
}
And the service implementations are in package fr.generali.nova.atp.service.metier.impl:
package fr.generali.nova.atp.service.metier.impl;
public class DefaultProduitGeneriqueServiceImpl implements IProduitGeneriqueService {
#Override
#Traceable(value = ETraceableMessages.VALIDATION_PRODUIT_GENERIQUE, hasReturnedValue=Traceable.HAS_NOT_RETURNS_VALUE)
public void valider(ElementNiveauUn element) {
// TODO: implement
}
}
Your aspect should look like this:
package fr.generali.nova.atp.logging.advisor;
#Aspect
public class TraceableAdvisor {
#Before(value = "execution(* fr.generali.nova.atp.service.metier.api.*.*(..)) && #annotation(traceable) && args(element)", argNames = "element,traceable")
public void traceWhenReturnedValueDoesntExistOrNotNecessary(ElementNiveauUn element, Traceable traceable) {
// TODO: implement
System.err.println("traced...");
}
}
The default proxy strategy for Spring AOP is JDK interface-based proxies, so Your pointcut expression should match the interface method execution, not the implementation method execution, and Your poincut expression may match either interface mothod execution or implementation method execution.
And remember to include an AspectJAutoProxyCreator in your configuration using for example <aspectj-autoproxy /> tag.
And here is a simple test to prove everyting is working:
public class TraceableAdvisorTest {
#Configuration
public static class TestConfiguration {
#Bean
public IProduitGeneriqueService produitGeneriqueService() {
return new DefaultProduitGeneriqueServiceImpl();
}
#Bean
public TraceableAdvisor traceableAdvisor() {
return new TraceableAdvisor();
}
#Bean
public AnnotationAwareAspectJAutoProxyCreator autoProxyCreator() {
return new AnnotationAwareAspectJAutoProxyCreator();
}
}
private AnnotationConfigApplicationContext testApplicationContext;
#Test
public void testTraceWhenReturnedValueDoesntExistOrNotNecessary() {
this.testApplicationContext = new AnnotationConfigApplicationContext();
this.testApplicationContext.register(TestConfiguration.class);
this.testApplicationContext.refresh();
IProduitGeneriqueService service = BeanFactoryUtils.beanOfType(this.testApplicationContext, IProduitGeneriqueService.class);
System.err.println("BEFORE");
service.valider(null);
System.err.println("AFTER");
}
}
The err output is:
BEFORE
traced...
AFTER
For all combinations:
fr.generali.nova.atp.service.metier.api.*.*(..)
fr.generali.nova.atp.service.metier.impl.*.*(..)
fr.generali.nova.atp.service.metier..*.*(..)
Make sure both beans are properly configured, either through annotations or in your appCtx.
It looks like your Aspect is definitely right, but how about the other class? Is it Spring enabled?
Also, if both classes are indeed configured correctly, are you sure that the instance being passed is a Spring bean and not a "new" instance from a constructor?

Resources