Hello i want to make a integration test for my db in h2. But i have some problem with my method.
My integration test code :
#RunWith(SpringRunner.class)
#DataJpaTest
public class Team_database_integration_test {
#MockBean
private TeamRepository teamRepository;
#Autowired
private TestEntityManager testEntityManager;
#Test
public void testDb() {
Team team = new Team(1L, "teamName", "teamDescription", "krakow", 7);
testEntityManager.persist(team);
testEntityManager.flush();
Assert.assertEquals(1L, teamRepository.findById(team.getId()));
}
}
My error:
java.lang.AssertionError:
Expected :1
Actual :Optional.empty
I think a reason is in my service method findbyid:
public Optional<TeamDto> findTeamById(Long id) {
Assert.notNull(id, "ID must exist ");
return teamRepository
.findById(id)
.map(p -> modelMapper.map(p, TeamDto.class));
}
Related
I am using Spring Boot and Spring Data Cassandra in my webflux application. I have issue when I do integration test with docker by using testcontainer, Spring doesn't inject the implementation of my cassandra repository in my tests but it always injects Mockito mock object.
For testing I have 2 kind of tests are unitTest and integrationTest
with properly profile test and integration.
Below are all my required classes and test classes
My cassandra repository using Spring Data Cassandra
#Repository
public interface UserGroupRepository extends CassandraRepository<UserGroup, String> {
}
For test profile
#ActiveProfiles("test")
#Configuration
public class TestConfig {
#MockBean
private CqlSession cassandraSession;
#MockBean
private CassandraConverter cassandraConverter;
#MockBean
public UserGroupRepository consumerSegmentRepository;
#MockBean
private CassandraMappingContext cassandraMapping;
#MockBean
private SessionFactoryFactoryBean cassandraSessionFactory;
#MockBean
private CassandraAdminTemplate cassandraAdminTemplate;
#MockBean
private SessionFactory sessionFactory;
}
For integration profile
My base integration test class which use docker for real cassandra database
public abstract class AbstractTest {
public static final String CASSANDRA_KEYSPACE = "user_group";
public static final String CASSANDRA_SCHEMA_SCRIPT = "cassandra/schema.cql";
public static final String CASSANDRA_IMAGE = "cassandra:3.11.2";
public static final int CASSANDRA_PORT = 9042;
private static final Logger log = LoggerFactory.getLogger(AbstractTest.class);
protected static final CassandraContainer CASSANDRA = new CassandraContainer(CASSANDRA_IMAGE);
static {
startCassandra();
}
protected static void startCassandra() {
CASSANDRA.withInitScript(CASSANDRA_SCHEMA_SCRIPT)
.withExposedPorts(CASSANDRA_PORT)
.withStartupTimeout(Duration.ofMinutes(5))
.withReuse(true)
.start();
System.setProperty("spring.data.cassandra.keyspace-name", CASSANDRA_KEYSPACE);
System.setProperty("spring.data.cassandra.contact-points", "localhost:" + CASSANDRA.getMappedPort(9042));
System.setProperty("spring.data.cassandra.local-datacenter", "datacenter1");
System.setProperty("spring.data.cassandra.schema-action", "create_if_not_exists");
}
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// Cassandra
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
applicationContext,
"spring.data.cassandra.keyspace-name=" + CASSANDRA_KEYSPACE,
"spring.data.cassandra.contact-points=" + "localhost:" + CASSANDRA.getMappedPort(CASSANDRA_PORT),
"spring.data.cassandra.local-datacenter=" + "datacenter1",
"spring.data.cassandra.schema-action=" + "create_if_not_exists"
);
Runtime.getRuntime().addShutdownHook(new Thread(CASSANDRA::stop));
}
}
protected static Session getCassandraSession() {
return CASSANDRA.getCluster().connect(CASSANDRA_KEYSPACE);
}
}
My integration test
#ActiveProfiles("integration")
#AutoConfigureWebTestClient
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureDataCassandra
#ContextConfiguration(initializers = AbstractTest.Initializer.class)
#EnableCassandraRepositories(basePackageClasses = {UserGroupRepository.class})
public class UserGroupControllerTest extends AbstractTest {
private static final Logger log = LoggerFactory.getLogger(UserGroupControllerTest.class);
#Autowired
private WebTestClient webTestClient;
#Autowired
private UserGroupRepository userGroupRepository;
#Test
void test_cassandra_upAndRunning() {
assertThat(CASSANDRA.isRunning()).isTrue();
Session session = getCassandraSession();
ResultSet result = session.execute("select * from user_group where group_id='group001'");
Row row = result.iterator().next();
assertEquals("User group 1", row.getString("group_name"));
}
#Test
public void test_getCurrentUserGroup_success() {
log.info("Instance of userGroupRepository {}", userGroupRepository.getClass().getName());
UserGroupDto userGroupDto = webTestClient.get()
.uri("/api/v1/usergroups")
.header(HttpHeaders.AUTHORIZATION, "Bearer abc")
.exchange()
.expectStatus()
.isOk()
.expectBody(UserGroupDto.class)
.returnResult()
.getResponseBody();
Assertions.assertNotNull(userGroupDto);
}
}
The injection code
#Autowired
private UserGroupRepository userGroupRepository;
Spring always inject Mockito bean for this repository, althought I try to use #EnableCassandraRepositories and #AutoConfigureDataCassandra but it seems not work.
My question is how to force Spring to inject the real implementation of my cassandra repository? Thanks.
The root cause is invalid usage of ActiveProfile and Profile in Spring. Update TestConfig to use #Profile instead of #ActiveProfiles.
I was practicing Junit5 with Mockito and doing parameterized testing on spring boot application
Postman request and responses tests show no issues with my code, but I could not accomplish with a junit5 test.
I get "InvoicationTargetException" whenever I do MockMvc perform(..) method call.
I have googled for possible solutions but I did not find what I was looking.
Initially, I was having issue cause I was not mocking the service, but and this problem started showing up. I find it difficult to debug the test with breakpoints, and sometimes debugging does not step in to a method call, and difficult to understand the problem.
I could not recreate another scenario also where MockMvc.perform task gets completed, but MvcResult would be empty, and I get an error "Unparsable json string". I do appreciate some directions on running the following junit test successfully.
Entity
Book: id(Integer), title(String),pubYear(String), author(Author)
Author: id(Integer), firstName(String), lastName(String), books(List)
AuthorRepository
#Repository
public interface AuthorRepository
extends CrudRepository<Author, Integer>{
#Query("select b from Author b where b.firstName= ?1 and b.lastName = ?2")
public Author findAuthorByName(String firstName, String lastName);
}
AuthorController
#RestController
#RequestMapping(value="/authors")
public class AuthorController {
#Autowired
private AuthorService authorService;
..................
#GetMapping(path = "/books/{first}/{last}")
public Book getBooksByAuthorName(#PathVariable String first, #PathVariable String last) {
Book book = this.authorService.getBooksByAuthorName(first, last);
return book;
}
}
AuthorService
#Service
public class AuthorService {
#Autowired
private AuthorRepository authorRepository;
#Autowired
private BookService bookService;
.................
public Book getBookByAuthorName(String first, String last) {
Author author = this.authorRepository.findAuthorByName(first, last);
Book b = author.getBooks().get(0);
return b;
}
AuthorControllerTest
#AutoConfigureMockMvc
public class AuthorControllerTest {
#Autowired
private MockMvc mockMvc;
#Mock
private AuthorService authorService;
//input for parameterized testing, currently only one test sample
public static Collection<Object[]> input() {
return Arrays.asList(new Object[][] {
{"Test-1",
"John",
"Banville",
"{ \"id\":1, \"title\":\"Eclipse\", \"pubYear\":\"2000\", \"author\":{\"id\":1, \"firstName\":\"john\", \"lastName\":\"banville\"}}"
}
});
}
#BeforeEach
public void initMOcks() {
MockitoAnnotations.initMocks(this);
}
#ParameterizedTest
#MethodSource("input")
public void testGetBooksByAuthor(String testId, String firstName, String lastName,
String expectedResponse) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Book book = null;
if(expectedResponse != null) {
book = mapper.readValue(expectedResponse, Book.class);
}
doReturn(book).when(authorService).getBookByAuthorName(firstName,lastName);
MvcResult result = mockMvc.perform(
MockMvcRequestBuilders.get("/authors/books/{first}/{last}", firstName,lastName))
.andReturn();
System.out.println(testId + "Result: " + result.getResponse().getContentAsString());
System.out.println(testId + "Expect: " + expectedResponse );
JSONAssert.assertEquals(expectedResponse, result.getResponse().getContentAsString(), true);
}
}
As #Kathrin Geilmann suggested I added #MockBean. I have [read][1] this is not mocking, but I tried that I got
.UnsatisfiedDependencyException: Error creating bean with name
'authorController': Unsatisfied dependency expressed through
constructor parameter 0; nested exception
then I added #MockBean for each nested dependencies. Below the arrow direction shows where those dependency beans are injected in the constructors in the classes I have.
AuthorController <-- AuthorService <-- (AuthorRepository, BookService)<-- Bookrepository
#MockBean
private AuthorService authorService;
#MockBean
private AuthorRepository authorRepository;
#MockBean
private BookService bookService;
Why BookReqpository is not considered "Unsatisfied dependency"? I run the test I get
NullInsteadOfMockException, Argument passed to when() is null;
This is the line of the code from original post
doReturn(book).when(authorService).getBooksByAuthorName(firstName,lastName);
This is where I switched #MockBean to #Mock for authorService but I got the same error. So I switched back to #MockBean
I have finally changed the test class annotation to use #WebMvcTest. This made the test run without any issue, and I also removed #AutoConfigureMockMvc did not create any problem.
#WebMvcTest
public class AuthorControllerTest {
Though I can run the test now, but I do not think I done any mocking, and I have read that #MockBean is not mocking.
[1]: https://www.baeldung.com/java-spring-mockito-mock-mockbean
Is it possible to use dependency injection with unit tests using Spring Boot? For integration testing #SpringBootTest start the whole application context and container services. But is it possible to enable dependency injection functionality at unit test granularity?
Here's the example code
#ExtendWith(SpringExtension.class)
public class MyServiceTest {
#MockBean
private MyRepository repo;
#Autowired
private MyService service; // <-- this is null
#Test
void getData() {
MyEntity e1 = new MyEntity("hello");
MyEntity e2 = new MyEntity("world");
Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
List<String> data = service.getData();
assertEquals(2, data.size());
}
}
#Service
public class MyService {
private final MyRepository repo; // <-- this is null
public MyService(MyRepository repo) {
this.repo = repo;
}
public List<String> getData() {
return repo.findAll().stream()
.map(MyEntity::getData)
.collect(Collectors.toList());
}
}
Or should I just manage the SUT (service class) as POJO and manually inject the mocked dependencies? I want to keep tests fast but minimize boilerplate code.
As #M.Deinum mentioned in the comments, unit tests shouldn't use dependency injection. Mock MyRepository and inject MyService using Mockito (and Junit5):
#ExtendWith(MockitoExtension.class)
public class MyServiceTest {
#InjectMocks
private MyService service;
#Mock
private MyRepository repo;
#Test
void getData() {
MyEntity e1 = new MyEntity("hello");
MyEntity e2 = new MyEntity("world");
Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
List<String> data = service.getData();
assertEquals(2, data.size());
}
}
If you want to test the repository, use #DataJpaTest. From the docs:
Using this annotation will disable full auto-configuration and instead
apply only configuration relevant to JPA tests.
#DataJpaTest
public class MyRepositorTest {
#Autowired
// This is injected by #DataJpaTest as in-memory database
private MyRepository repo;
#Test
void testCount() {
repo.save(new MyEntity("hello"));
repo.save(new MyEntity("world"));
assertEquals(2, repo.count());
}
}
In conclusion, the suggested approach is to test the service layer mocking the repository layer with Mockito (or similar library) and to test the repository layer with #DataJpaTest.
You have not added the #Autowired in service for MyRepository
Service Class
#Service
public class MyService {
private final MyRepository repo; // <-- this is null
#Autowired
public MyService(MyRepository repo) {
this.repo = repo;
}
public List<String> getData() {
return repo.findAll().stream()
.map(MyEntity::getData)
.collect(Collectors.toList());
}
}
Service Test Class
#ExtendWith(MockitoExtension.class)
public class MyServiceTest {
#Mock
private MyRepository repo;
#InjectMocks
private MyService service;
#Test
void getData() {
MyEntity e1 = new MyEntity("hello");
MyEntity e2 = new MyEntity("world");
Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
List<String> data = service.getData();
assertEquals(2, data.size());
}
}
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);
}
}
I'am trying to do a simple Integration test using Spring Boot Test in order to test the e2e use case. My test does not work because I'am not able to make the repository saving data, I think I have a problem with spring contexts ...
This is my Entity:
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Person {
#Id
private int id;
private String name;
}
This is the Person repository:
#Repository
public interface PersonRepository extends JpaRepository<Person, Integer> {
}
The Person service:
#Service
public class PersonService {
#Autowired
private PersonRepository repository;
public Person createPerson(int id,String name) {
return repository.save(new Person(id, name));
}
public List<Person> getPersons() {
return repository.findAll();
}
}
The Person Controller:
#RequestMapping
#RestController
public class PersonController {
#Autowired
private PersonService personService;
#RequestMapping("/persons")
public List<Person> getPersons() {
return personService.getPersons();
}
}
The main Application class:
#SpringBootApplication
public class BootIntegrationTestApplication {
public static void main(String[] args) {
SpringApplication.run(BootIntegrationTestApplication.class, args);
}
}
The application.properties file:
spring.datasource.url= jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
And the Test:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BootIntegrationTestApplicationTests {
#Autowired
private PersonService personService;
#Autowired
private TestRestTemplate restTemplate;
#Test
#Transactional
public void contextLoads() {
Person person = personService.createPerson(1, "person1");
Assert.assertNotNull(person);
ResponseEntity<Person[]> persons = restTemplate.getForEntity("/persons", Person[].class);
}
}
The test does not work, because the service is not saving the Person entity ....
Thanks in advance
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
public class SmokeTest {
#Autowired
UserController userController;
#Autowired
UserDao userDAO;
#Rollback(false) // This is key to avoid rollback.
#Test
public void contextLoads() throws Exception {
System.out.println("Hiren");
System.out.println("started");
userDAO.save(new User("tyx", "x#x.com"));
}
}
Refer #Rollback(false) is key to avoid rollback.
Thanks to M. Deinum, I think I get the point,
So the best is to separate the logic of the test into two tests, the first will testing just the service (so this one could be transactional) and the second the controller:
Test 1:
#Test
#Transactional
public void testServiceSaveAndRead() {
personService.createPerson(1, "person1");
Assert.assertTrue(personService.getPersons().size() == 1);
}
Test 2:
#MockBean
private PersonService personService;
#Before
public void setUp() {
//mock the service
given(personService.getPersons())
.willReturn(Collections.singletonList(new Person(1, "p1")));
}
#Test
public void testController() {
ResponseEntity<Person[]> persons = restTemplate.getForEntity("/persons", Person[].class);
Assert.assertTrue(persons.getBody()!=null && persons.getBody().length == 1);
}
Spring for saving entity requires transaction. But until transaction has been commited changes not be visible from another transaction.
Simplest way is call controller after commit transaction
#Test
#Transactional
public void contextLoads() {
Person person = personService.createPerson(1, "person1");
Assert.assertNotNull(person);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() {
ResponseEntity<Person[]> persons = restTemplate.getForEntity("/persons", Person[].class);
}
});
}
For each #Test function that makes a DB transaction, if you want to permanently persist the changes, then you can use #Rollback(false)
#Rollback(false)
#Test
public void createPerson() throws Exception {
int databaseSizeBeforeCreate = personRepository.findAll().size();
// Create the Person
restPersonMockMvc.perform(post("/api/people")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(person)))
.andExpect(status().isCreated());
// Validate the Person in the database
List<Person> personList = personRepository.findAll();
assertThat(personList).hasSize(databaseSizeBeforeCreate + 1);
Person testPerson = personList.get(personList.size() - 1);
assertThat(testPerson.getFirstName()).isEqualTo(DEFAULT_FIRST_NAME);
assertThat(testPerson.getLastName()).isEqualTo(DEFAULT_LAST_NAME);
assertThat(testPerson.getAge()).isEqualTo(DEFAULT_AGE);
assertThat(testPerson.getCity()).isEqualTo(DEFAULT_CITY);
}
I tested it with a SpringBoot project generated by jHipster:
SpringBoot: 1.5.4
jUnit 4.12
Spring 4.3.9
Pay your attention to the order in which the tests are executed, the tests with the #Commit or #Rollback(false) annotation must be executed first: https://www.baeldung.com/junit-5-test-order
Do not use #Rollback(false). Unit Test should not generate data.
JPA FlushMode is AUTO (default - flush INSERT/UPDATE/DELETE SQL when query occurs) / COMMIT.
Just query the working entity for forcing FLUSH, or using EntityManager to force flush
#Test
public void testCreate(){
InvoiceRange range = service.createInvoiceRange(1, InvoiceRangeCreate.builder()
.form("01GTKT0/010")
.serial("NV/18E")
.effectiveDate(LocalDate.now())
.rangeFrom(1L)
.rangeTo(1000L)
.build(), new byte[] {1,2,3,4,5});
service.findByCriteria(1, "01GTKT0/010", "NV/18E"); // force flush
// em.flush(); // another way is using entityManager for force flush
}