Spring Junit test entity not saved to repository - java

I'm trying to test a service method that lists all entities. The method looks like this:
#Test
public void listAllProfiles() {
Profile sampleProfile = Profile.createWithDefaultValues();
profileRepository.save(sampleProfile);
ProfileService profileService = new ProfileService(profileRepository, new ModelMapper());
List<ProfileDto> profiles = profileService.listAllProfiles();
ProfileDto lastProfile = profiles.get(profiles.size() - 1);
assertEquals(sampleProfile.getId(), lastProfile.getId());
}
The test fails cause IndexOutOfBoundsException: Index -1 out of bounds for length 0
I found out that the sampleProfile is not probably saved during the test. When I log profileRepository.findAll().size() the value is always 0.
What is worng with my test?

If you want to test your ProfileService you have to mock the profileRepository cause if you don't do that then you are actually doing an integration test.
Unit testing is all about testing small units if there are any dependencies on those units you have to mock them (either manually or with a framework, Mockito is the most common one in the Java world).
If your service is using the repository to fetch all the profiles you have to mock that call, something like this:
List<Profile> profilesList = Arrays.asList(new Profile("Profile 1"), new Profile("Proiile 2"));
given(profileRepository.findAll()).willAnswer((Answer<List>) invocation -> profilesList);
So you shouldn't save anything in the database (actually, you shouldn't interact with a database at all when you are unit testing), you just mock the repository you are using on your service. Here is a project I wrote some months ago where I actually solved the exact same issue.

Related

Testing conditions and exceptions in Integration Tests?

I have written several Unit Tests and now switched to write Integration Test in our Java (Spring Boot) app. We use JUnit and Mockito libraries for testing.
As far as I know, Integration Tests check the entire rings rather than a function. However, I am confused that if I should also check the if conditions in the methods while integration testing. Here is an example service method:
#Override
public CountryDTO create(CountryRequest request) {
if (countryRepository.existsByCodeIgnoreCase(countryCode)) {
throw new EntityAlreadyExistsException();
}
final Country country = new Country();
country.setCode("UK");
country.setName("United Kingdom");
final Country created = countryRepository.save(country);
return new CountryDTO(created);
}
My questions are:
1. Can I write integration test for a Service or a Repository class?
2. when I test create method in my service above, I think I just create the proper request values (CountryRequest) in my Test class, then pass them to this create method and then check the returned value. Is that true? Or do I also need to test the condition in the if clause (countryRepository.existsByCodeIgnoreCase(countryCode))?
3. When I test find methods, I think I should first create record by calling create method and the proper place for this is #BeforeEach setup() {} method. Is that true?
If you wrote Unit tests that made sure, your services and repositories are working correctly (for example by validation and parameterized tests) I believe, you don't have to write integration tests for them.
You should write integration tests to check the behavior of your app. By testing if your controller is working correctly you will also check if service and repo are ok.
I believe unit test should check it.
Do you ask if you should create record in db? If you want to test if repository is correctly communicating with service and it with controller, you have to do it with some data.

Testing a service method by Unit Test? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I have no test experience and try to test a method by a Unit Test. All the examples that I have a look at perform operations via uses mock values. I know, I will also use mock values with mockito that I use in my project. Here is my service method that I want to test:
ProductServiceImpl:
public List<ProductDTO> findAllByCategoryUuid(UUID categoryUuid) {
// code omitted
return result;
}
Here is my Unit Test class:
ProductServiceImplTest:
// ? #InjectMocks
#Autowired
ProductServiceImpl productService;
#Mock
ProductRepository productRepository;
#Test
public void testFindAllByCategoryUuid() {
UUID categoryUuid = UUID.randomUUID();
final List<Product> productList = new ArrayList<>();
for (int i = 0; i < size; i++) {
// create product by setting "categoryUuid" and add to productList
}
productRepository.saveAll(productList);
when(productService.findAllByCategoryUuid(categoryUuid)
.thenReturn(productList);
}
My questions:
1. Is the approach above is correct in order to test the service method? I think I should not deal with inside the service method and just pass categoryUuid and check the result of that method for testing? Is that true?
2. In test class, I used #Autowired to access service method, but I am not sure if I should #Mock. Is there any mistake?
Any help would be appreciated.
Update: I also create unit test using DiffBlue plugin and it generates a test method as shown below. But I think it seems to be as testing repository methods rather than the service method. Is not it?
#Test
public void testFindAllByCategoryUuid() {
when(this.productRepository.findAllByCategoryUuid((UUID) any()))
.thenReturn(new ArrayList<Product>());
assertTrue(this.productService.findAllByCategoryUuid(UUID.randomUUID())
.isEmpty());
verify(this.productRepository).findAllByCategoryUuid((UUID) any());
// ...
}
I am not an expert but I will try to answer you question
The general approach to unit test your method should be to test the output against all possible set of inputs.
in your specific case you can test
input: existing UUID output : NonNull List.
input: non existing UUID output : Empty List.
input: null : Empty List.
Now what you have done here is right you need to Autowire the class that you are writing test cases for and mock the dependencies in that class.
Only thing wrong is
when(productService.findAllByCategoryUuid(categoryUuid)
.thenReturn(productList);
should be
when(productRepository.findAllByCategoryUuid(categoryUuid)
.thenReturn(productList);
here you are mocking productRepository.findAllByCategoryUuid as your goal is to test the method in service class.
after this just add appropriate assert statements for all the conditions mentioned above.
Also I usually follow a rule whenever bug is logged against some code I try to cover that input and output case using assert in my Junit so that every-time I will test all the possible input and output scenarios.
The important things to remember while writing Junit Tests using Mockito
All class level #Runwith()
Test class should be with #InjectMocks
All tests should be annotated with #Test
Any external service should be Mocked with #Mock
Any calls going to DB or other services should be mocked and values should be returned accordingly.
You should have assertions to test your result.
I would write something like this :
#RunWith(MockitoJUnitRunner.class)
public class ProductServiceImplTest {
#InjectMocks
ProductServiceImpl productService;
#Mock
ProductRepository productRepository;
#Test
public void testFindAllByCategoryUuid() {
UUID categoryUuid = UUID.randomUUID();
final List<Product> productList = new ArrayList<>();
for (int i = 0; i < size; i++) {
// create product by setting "categoryUuid" and add to productList
}
when(productRepository.saveAll(ArgumentMatchers.any()).thenReturn(productList);
// Or below might work for newer version of test cases when we get Null Pointer Exp using older version of Junit test cases
//doReturn(productList).when(productRepository).saveAll(any(List.class));
List<ProductDTO> response = productService.findAllByCategoryUuid(categoryUuid);
Assert.assertNotNull(response);
Assert.assertEquals("Object Value", response.getXXX());
}
Writing unit tests against Service layer comes with drawbacks:
You violate encapsulation of the method-under-test. If it changes because you start to invoke different classes/methods - the test will break. Even though the method may be working correctly.
Because you intend to use mocking, partly your tests will be simply checking that your mocks are set up to pass the tests. So basically you'll be testing test logic.
It's usually more productive to move the logic down e.g. to the Model. Those classes could be then unit-tested without mocks. Then you could write a higher-level test (including DB) that checks that everything works together.
Reading:
Anemic architecture - enemy of testing
Building Test Pyramid to optimize automated testing

How to cleanup h2 db after each junit test?

I am making junit tests which adds some users before each test case. The code is:
#BeforeEach
void setUp() {
saveUser();
saveEntry();
}
#Test
void saveUser() {
User user = new User();
user.setUserId(null);
user.setUsername("John");
user.setEmail("john#foo.com");
user.setPassword("password");
userService.saveUser(user);
}
#Test
void saveEntry() {
Entry entry = new Entry();
entry.setText("test text");
entry.setUserId(1L);
entryService.saveEntry(entry);
}
As you see I am using the methods that I have in my service layer to create entries and users. If I run the tests one by one there is no problem. But when I run all tests then db is not returning 1 item and returning multiple items so exception occurs.
I need to cleanup h2 db after each test with maybe #AfterEach annotation but I do not have and delete method in my code to invoke. How can I cleanup the H2 db ?
In addition to #J Asgarov answer which is correct providing you use spring-boot if you want to perform some actions before and after each test (more specifically before #Before and after #After methods) you can use #Sql annotation to execute specific sql script for example from test resources.
#Sql("init.sql")
#Sql(scripts = "clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public class TestClass
}
It might be handy for sequences, since they don't care of rollback.
Regarding #Transactional mentioned by #Mark Bramnik be careful, because the transaction spans for entire test method, so you cannot verify correctness of the transaction boundaries.
You've mentioned spring and it looks like you're testing DAO layer, so I assume the tests are running with SpringExtension/SpringRunner if you're on junit 4.
In this case,
Have you tried to use #Transactional on the test method? Or alternatively if all the test methods are "transactional" you can place it once on a test class.
This works as follows:
If everything is configured correctly spring will open a transaction before the test starts, and will rollback that transaction after the test ends.
The rollback is supposed to clean the inserted data automatically.
Quick googling revealed this tutorial, surely there are many others
#JpaDataTest annotating the test class will rollback every test by default
https://www.baeldung.com/spring-jpa-test-in-memory-database

Junit testing in a Spring / Maven project

I have a problem writing a Junit testcase for a Spring project. Its about the following method;
boolean doesUserIdExist(String userId){
if(userRepository.findOne(userId.toLowerCase()) != null) {
throw new userAlreadyExistsException("User with id: " + userId + " already exists")
return false;
}else{
return true;
}
}
Now in my jUnit I have something written like this..:
void compareDuplicateUserIdTest (){
UserService UserService = new UserService();
String lowercase = "test";
String uppercase = "Test";
boolean result = userService.doesUserIdExist(lowercase);
//Check the boolean result if its true
}
Since im using the findOne method it means that i'd have to check the String = "test" against the DB userId = "test". This is not the right way since it should work standalone without any records in the MongoDB database.
Now i've been reading about a framework like mockito to test this, but isn't this "too much" for such a simple method check? Can I remove the findOne part and just compare the strings?
You are facing a very common problem for unit testing where databases should not be involved (that would be integration testing), so... here is where Mockito is a great tool to use.
Using Mockito allows you to mock your database results and continue with the regular flow of your method, so you could do something like this:
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
#RunWith(MockitoJUnitRunner.class)
public class UserService_doesUserIdExistTests
{
#Mock
private UserRepository userRepository;
#InjectMocks
private UserService userService;
#Test
void compareDuplicateUserIdTest() {
String lowercase = "test";
// Mocking the response for your method that uses external dependencies
when(userRepository.findOne(lowercase)).thenReturn(true); // You can mock the response you want using .thenReturn(...)
// Test your userService method (you can also debug it if needed)
boolean result = userService.doesUserIdExist(lowercase);
//Check the boolean result if its true
assertTrue(result);
}
}
I have not tested the code but shows the idea of testing userService.doesUserIdExist(...) method as a unit. Also this is quite helpful when you need to learn by debugging code.
No, it is not too much. The idea of Unit tests is to test method behaviour based on different inputs and insure it behaves as expected. It also documents your expectations and makes refactoring much less painful.
Imagine in the future somebody (may be even you) will decide to remove userRepository.findOne from doesUserExist method. Your unit test will fail and then the developer will have to figure out if the tests need to be changed due to rafactoring or the refactoring needs to be fixed.
I am not even sure what you'll be testing if you remove findOne method and how you are planning to do that.
Mockito and Spring make mocking really simple. All you need to do is define and initiate mock userRepository and then define the behaviour based on the input.

Unit testing with MongoDB

My database of choice is MongoDB. I'm writing a data-layer API to abstract implementation details from client applications - that is, I'm essentially providing a single public interface (an object which acts as an IDL).
I'm testing my logic as I go in a TDD manner. Before each unit test, an #Before method is called to create a database singleton, after which, when the test completes, an #After method is called to drop the database. This helps to promote independence among unit tests.
Nearly all unit tests, i.e. performing a contextual query, require some kind of insertion logic to occur before hand. My public interface provides an insert method - yet, it seems incorrect to use this method as precursor logic to each unit test.
Really I need some kind of mocking mechanism, yet, I haven't had much experience with mocking frameworks, and it seems that Google returns nothing re a mocking framework one might use with MongoDB.
What do others do in these situations? That is, how do people unit test code that interacts with a database?
Also, my public interface connects to a database defined in a external configuration file - it seems incorrect to use this connection for my unit testing - again, a situation that would benefit from some kind of mocking?
Technically tests that talk to a database (nosql or otherwise) are not unit tests, as the tests are testing interactions with an external system, and not just testing an isolated unit of code. However tests that talk to a database are often extremely useful, and are often fast enough to run with the other unit tests.
Usually I have a Service interface (eg UserService) which encapsulates all the logic for dealing with the database. Code that relies on UserService can use a mocked version of UserService and is easily tested.
When testing the implementation of the Service that talks to Mongo, (eg MongoUserService) it is easiest to write some java code that will start/stop a mongo process on the local machine, and have your MongoUserService connect to that, see this question for some notes.
You could try to mock the functionality of the database while testing MongoUserService, but generally that is too error prone, and doesn't test what you really want to test, which is interaction with a real database. So when writing tests for MongoUserService, you set up a database state for each test. Look at DbUnit for an example of a framework for doing so with a database.
As sbridges wrote in this post it is a bad idea not to have a dedicated service (sometimes also known as repository or DAO) which abstracts the data access from the logic. Then you could test the logic by providing a mock of the DAO.
Another approach which I do is to create a Mock of the Mongo object (e.g. PowerMockito) and then return the appropriate results.
This because you don't have to test if the database works in unit tests but more over you should test if the right query was sent to the databse.
Mongo mongo = PowerMockito.mock(Mongo.class);
DB db = PowerMockito.mock(DB.class);
DBCollection dbCollection = PowerMockito.mock(DBCollection.class);
PowerMockito.when(mongo.getDB("foo")).thenReturn(db);
PowerMockito.when(db.getCollection("bar")).thenReturn(dbCollection);
MyService svc = new MyService(mongo); // Use some kind of dependency injection
svc.getObjectById(1);
PowerMockito.verify(dbCollection).findOne(new BasicDBObject("_id", 1));
That would also be an option. Of course the creation of the mocks and returning of the appropriate objects is just coded as an example above.
I wrote a MongoDB fake implementation in Java: mongo-java-server
Default is a in-memory backend, that can be easily used in Unit and Integration tests.
Example
MongoServer server = new MongoServer(new MemoryBackend());
// bind on a random local port
InetSocketAddress serverAddress = server.bind();
MongoClient client = new MongoClient(new ServerAddress(serverAddress));
DBCollection coll = client.getDB("testdb").getCollection("testcoll");
// creates the database and collection in memory and inserts the object
coll.insert(new BasicDBObject("key", "value"));
assertEquals(1, collection.count());
assertEquals("value", collection.findOne().get("key"));
client.close();
server.shutdownNow();
Today I think the best practice is to use testcontainers library (Java) or testcontainers-python port on Python. It allows to use Docker images with unit tests.
To run container in Java code just instantiate GenericContainer object (example):
GenericContainer mongo = new GenericContainer("mongo:latest")
.withExposedPorts(27017);
MongoClient mongoClient = new MongoClient(mongo.getContainerIpAddress(), mongo.getMappedPort(27017));
MongoDatabase database = mongoClient.getDatabase("test");
MongoCollection<Document> collection = database.getCollection("testCollection");
Document doc = new Document("name", "foo")
.append("value", 1);
collection.insertOne(doc);
Document doc2 = collection.find(new Document("name", "foo")).first();
assertEquals("A record can be inserted into and retrieved from MongoDB", 1, doc2.get("value"));
or on Python (example):
mongo = GenericContainer('mongo:latest')
mongo.with_bind_ports(27017, 27017)
with mongo_container:
def connect():
return MongoClient("mongodb://{}:{}".format(mongo.get_container_host_ip(),
mongo.get_exposed_port(27017)))
db = wait_for(connect).primer
result = db.restaurants.insert_one(
# JSON as dict object
)
cursor = db.restaurants.find({"field": "value"})
for document in cursor:
print(document)
I'm surprised no one advised to use fakemongo so far. It emulates mongo client pretty well, and it all runs on same JVM with tests - so integration tests become robust, and technically much more close to true "unit tests", since no foreign system interaction takes place. It's like using embedded H2 to unit test your SQL code.
I was very happy using fakemongo in unit tests that test database integration code in end-to-end manner. Consider this configuration in test spring context:
#Configuration
#Slf4j
public class FongoConfig extends AbstractMongoConfiguration {
#Override
public String getDatabaseName() {
return "mongo-test";
}
#Override
#Bean
public Mongo mongo() throws Exception {
log.info("Creating Fake Mongo instance");
return new Fongo("mongo-test").getMongo();
}
#Bean
#Override
public MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongo(), getDatabaseName());
}
}
With this you can test your code that uses MongoTemplate from spring context, and in combination with nosql-unit, jsonunit, etc. you get robust unit tests that cover mongo querying code.
#Test
#UsingDataSet(locations = {"/TSDR1326-data/TSDR1326-subject.json"}, loadStrategy = LoadStrategyEnum.CLEAN_INSERT)
#DatabaseSetup({"/TSDR1326-data/dbunit-TSDR1326.xml"})
public void shouldCleanUploadSubjectCollection() throws Exception {
//given
JobParameters jobParameters = new JobParametersBuilder()
.addString("studyId", "TSDR1326")
.addString("execId", UUID.randomUUID().toString())
.toJobParameters();
//when
//next line runs a Spring Batch ETL process loading data from SQL DB(H2) into Mongo
final JobExecution res = jobLauncherTestUtils.launchJob(jobParameters);
//then
assertThat(res.getExitStatus()).isEqualTo(ExitStatus.COMPLETED);
final String resultJson = mongoTemplate.find(new Query().with(new Sort(Sort.Direction.ASC, "topLevel.subjectId.value")),
DBObject.class, "subject").toString();
assertThatJson(resultJson).isArray().ofLength(3);
assertThatDateNode(resultJson, "[0].topLevel.timestamp.value").isEqualTo(res.getStartTime());
assertThatNode(resultJson, "[0].topLevel.subjectECode.value").isStringEqualTo("E01");
assertThatDateNode(resultJson, "[0].topLevel.subjectECode.timestamp").isEqualTo(res.getStartTime());
... etc
}
I used fakemongo without problems with mongo 3.4 driver, and community is really close to release a version that supports 3.6 driver (https://github.com/fakemongo/fongo/issues/316).

Categories

Resources