Junit how to achieve100% coverage for Model class - spring

I have a model class:
#Builder
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
public class Employee {
#GeneratedValue(strategy = GenerationType.AUTO)
#Type(type="uuid-char")
#Column(updatable = false, nullable = false, unique = true)
#Id
private UUID id;
#Column(updatable = true, nullable = false, unique = true)
#Email(message = "Enter a valid email")
private String email;
#NotNull(message = "First name cannot be empty")
#Size(min = 3, message = "First name character must be more than 3!")
private String firstName;
#Size(min = 3, message = "Last name character must be more than 3!")
private String lastName;
#Range(min = 21, max = 55, message = "Age must be between 21 and 55")
private int age;
#JsonIgnore
private Double accBalance;
#NotNull(message = "Gender cannot be empty")
private String gender;
#NotNull(message = "Country cannot be empty")
private String country;
#JsonProperty("Job Scope")
private String designation;
#CreationTimestamp
private Date createdAt;
#DateTimeFormat
private Date birthDate;
}
And this is my test class:
class EmployeeTest {
#Test
public void testObjectMethod() {
Employee object = new Employee();
object.equals(new Employee());
object.hashCode();
object.toString();
}
#Test
public void testAll() {
Employee object = new Employee();
object.equals(Employee.builder().build());
}
}
And this is my coverage. Basically it only covers 73.8%. What other tests do I need to do to achieve 100%? As this covers quite a lot and doesn't need much of thinking, I would like to target 100%. Appreciate any help or pointers.
coverage

You need to do following
write test for equals
write test for hashcode
write test case for constructor no-arg and all arg
test case for setter and getter for all attribute
you can write assertNotNull for hashCode various tests.

Related

Spring boot table to show list of pets by owner id

I'm making a small school project Spring Boot web application. Right now I have made CRUD for Owners table in the database, what I'm trying to do next is when I click button "pets" I want to be able to show only those pets that has the same "owner_id". I guess I should receive "owner_id" from the button that was pressed. How can I make that it works the way it should work? Now when I press button "pets" it shows all list of the pets.
Owner class:
#Entity
#Table(name = "owners")
public class Owner {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name", nullable = false)
private String lastName;
#Column(name = "email", nullable = false)
private String email;
#OneToMany(targetEntity = Pet.class,cascade = CascadeType.ALL)
#JoinColumn(name = "owner_id", referencedColumnName = "id")
private List<Pet> pets;
public Owner() {
}
public Owner(String firstName, String lastName, String email) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
}
Pet class:
#Table(name = "pets")
public class Pet {
#Id
private Long id;
private String name;
private String breed;
private int age;
private double weight;
public Pet() {
}
public Pet(String name, String breed, int age, double weight) {
super();
this.name = name;
this.breed = breed;
this.age = age;
this.weight = weight;
}
}
Controller method for list of pets:
#GetMapping("/owner_pets")
public String getAllPetsByOwnerId(Model model) {
model.addAttribute("pets", petService.getAllPetsByOwnerId());
return "owner_pets";
}
Here is the code written so far but it only shows list of all pets
I saw your service method for PerService. I do not see any ownerId being passed to findByOwnerId method. That might be the reason why you are getting all pets in response. What you should ideally do is
package com.veterinary.Veterinary_system.service;
import java.util.List;
import com.veterinary.Veterinary_system.entity.Pet;
public interface PetService {
//Repository declaration
List < Pet > findByOwnerId(Long ownerId){
return petRepository.findByOwnerId(ownerId);
}
Pet savePet(Pet pet);
}

Can't get products by userId from repository

I have 2 tables. One of them called 'products'
#Data
#Entity
#Table(name = "products")
#NoArgsConstructor
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(length = 100)
#NotBlank(message = "Name must be written")
private String name;
#Column(length = 200)
#NotBlank(message = "Provide image (link in this case) of your product")
private String image;
#PositiveOrZero
private int amount;
#Column(length = 250)
#NotBlank(message = "description must be written")
#Size(min = 10, max = 250, message = "description is too long or empty")
private String description;
#PositiveOrZero
private float price;
#ManyToOne
#JoinColumn(name = "type_id")
private ProductType productType;
public Product(#NotBlank String name, String image, int amount, #NotBlank String description,
#PositiveOrZero float price, ProductType productType) {
this.name = name;
this.image = image;
this.amount = amount;
this.description = description;
this.price = price;
this.productType = productType;
}
}
another table is 'users'
#Data
#Entity
#NoArgsConstructor
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(length = 50)
#Size(min = 2, max = 30, message = "enter appropriate amount of letters, min 2")
private String username;
#Column(length = 100)
#Email(message = "Enter a valid email")
#NotBlank(message = "email should have a value")
private String email;
#Column(length = 50)
#NotBlank(message = "password should have a value")
#Size(min = 6, message = "password should at least consist of 6 characters")
private String password;
private boolean enabled;
private String role;
public User(#Size(min = 2, max = 30, message = "enter appropriate amount of letters, min 2")
String username,
#Email(message = "Enter a valid email")
#NotBlank(message = "email should have a value") String email,
#NotBlank(message = "password should have a value")
#Size(min = 6, message = "password should at least consist of 6 characters")
String password, boolean enabled, String role) {
this.username = username;
this.email = email;
this.password = password;
this.enabled = enabled;
this.role = role;
}
}
and also table that include both 'product_user' (many to many relationship)
it looks like this
#Data
#Entity
#Table(name = "product_user")
#AllArgsConstructor
#NoArgsConstructor
public class ProdAndUser{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id;
#ManyToOne
#JoinColumn(name = "product_id")
Product product;
#ManyToOne
#JoinColumn(name = "user_id")
User user;
public ProdAndUser(Product product, User user) {
this.product = product;
this.user = user;
}
}
then I tried to get them from prodAndUser repository by UserId or by User as obj:
#Repository
public interface ProdAndUserRepository extends JpaRepository<ProdAndUser, Integer> {
List<ProdAndUser> getProdAndUsersByUserId(Integer id);
List<ProdAndUser> getAllByUser(User user);
}
my controller looks like this:
#ResponseBody
#GetMapping("/findByUsr/{user}")
public List<ProdAndUser> getByUser(#PathVariable User user){
return prodAndUserRepository.getAllByUser(user);
}
error:
{
"timestamp": "2022-02-12T05:52:53.165+00:00",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/Cart/findByUsr"
}
I have tried to find them all by .findAll() and it worked fine. Also another tables work fine on their own
Look at the error it says (404) means something is not right with the path.
The path on the error does not contain user_id.

Hibernate #OneToMany relation cascade option not working

I design simple 1:N schema , Account(1):AccountProfileImage(N).
Below codes are entity codes.
// Account.java
#Entity
#Table(name = "account")
#Getter
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#EntityListeners(AuditingEntityListener.class)
public class Account {
#GeneratedValue
#Id
#Column(name = "id")
private Long id;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "password", nullable = false)
private String password;
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name", nullable = false)
private String lastName;
#CreatedDate
#Column(name = "created_at")
private LocalDateTime createdAt;
#OneToMany(mappedBy ="account",cascade = CascadeType.ALL)
private final List<AccountProfileImage> profileImages= new ArrayList<>();
#Builder
public Account(String email,String firstName,String lastName,String password){
this.email=email;
this.firstName=firstName;
this.lastName=lastName;
this.password=password;
}
}
// AccountProfileImage.java
#Entity
#Table(name = "account_profile_image")
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#Getter
#EntityListeners(AuditingEntityListener.class)
public class AccountProfileImage {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "account_id")
private Account account;
#Column(name = "image_url")
private String imageURL;
#CreatedDate
#Column(name = "created_at")
private LocalDateTime createdAt;
#Builder
public AccountProfileImage (Account account,String imageURL){
this.account=account;
// this.account.addProfileImage(this);
this.imageURL=imageURL;
}
}
and this is test code for AccountProfileRepository code.
#Test
#Rollback(value = false)
public void saveAccountProfileImageTest() throws Exception {
// given
Account account = Account.builder()
.email("user#email.com")
.firstName("user")
.lastName("user")
.password("1234")
.build();
AccountProfileImage profileImage = AccountProfileImage.builder()
.account(account)
.imageURL("pathToURI")
.build();
AccountProfileImage profileImage2 = AccountProfileImage.builder()
.account(account)
.imageURL("pathToURI2")
.build();
accountRepository.save(account);
// when
List<AccountProfileImage> images = profileImageRepository.findAllByAccount_IdOrderByCreatedAtDesc(1L);
// then
// this assertion fail
assertThat(images.size()).isEqualTo(2);
}
What i want to expect find by List of images whose size is 2 because I add CscadeType.ALL in Account entity class and when creating AccountProfileImage object, I set account member variable in AccountProfileImage object.
this.account=account;
Did I something wrong?
I add below method in Account entity and 2 lines at test code, then it works fine. Do i have to do this everytime? Is there exist another better approach or best practice?
// Account Entity
public void addProfileImages(AccountProfileImage image){
this.profileImages.add(image);
}
// test code
account.addProfileImages(profileImage);
account.addProfileImages(profileImage2);
accountRepository.save(account);
// when
List<AccountProfileImage> images = profileImageRepository.findAllByAccount_IdOrderByCreatedAtDesc(1L);
// then
// this assertion pass
assertThat(images.size()).isEqualTo(2);
In Bi-directional relationships, you have to define the association on both ends of the relationship. To avoid any issues, you can update the helper addProfileImage(..) method to add the AccountProfileImage to the list and set account property of the image to the current account. This is the best practice as this way, the helper method will set up the association across both ends of the bi-directional relationship.
E.g.
public void addProfileImages(AccountProfileImage image){
this.profileImages.add(image); // Add image to profileImages
image.setAccount(this); // Set account property to the current account
}
Test:
#Test
#Rollback(value = false)
public void saveAccountProfileImageTest() throws Exception {
// given
Account account = Account.builder()
.email("user#email.com")
.firstName("user")
.lastName("user")
.password("1234")
.build();
AccountProfileImage profileImage = AccountProfileImage.builder()
.imageURL("pathToURI")
.build();
AccountProfileImage profileImage2 = AccountProfileImage.builder()
.imageURL("pathToURI2")
.build();
// Setup association
account.addProfileImage(profileImage);
account.addProfileImage(profileImage2);
accountRepository.save(account);
// when
List<AccountProfileImage> images = profileImageRepository.findAllByAccount_IdOrderByCreatedAtDesc(1L);
// then
// this assertion fail
assertThat(images.size()).isEqualTo(2);
}

JPA Failing to produce a proper SQL query when a parameter has a composite primary key

Today I came across a weird bug while trying to test a JPA update query and I'm wondering if this a SpringBoot bug.
I have the following entities
An Entry entity
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
public class Entry {
#Id
private String id;
#ManyToOne(targetEntity = User.class)
#JoinColumn(referencedColumnName = "username")
#NotNull
private final User username;
#Enumerated(EnumType.STRING)
#NotNull
private Type type;
#ManyToOne(targetEntity = Category.class)
#JoinColumns({#JoinColumn(referencedColumnName = "name"),#JoinColumn(referencedColumnName = "type"),#JoinColumn(referencedColumnName = "username")})
#NotNull
private Category category;
#Size(max = 45)
#NotBlank
private String description;
#NotNull
private Double amount;
#NotNull
private final Date createdAt;
private Timestamp lastUpdate;
#NotNull
private Boolean isDeleted;
public enum Type{
Income,Expense
}
}
A Category entity with a composite key
#Entity
#NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
#Setter
#Getter
#EqualsAndHashCode(of = {"id"})
#ToString(of = {"id"})
public class Category {
#EmbeddedId
private CategoryId id;
private final Timestamp createdAt = Timestamp.from(Instant.now());
#ManyToOne(targetEntity = User.class)
#JoinColumn(referencedColumnName = "username")
private final User user;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "category")
private List<Entry> entries;
public Category(String name, Type type, User user){
this.id = new CategoryId(name,type,user.getUsername());
this.user = user;
}
}
A CategoryID that is the embeddable composite key of the Category entity
#Data
#Embeddable
#AllArgsConstructor
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#EqualsAndHashCode(of = {"name","type","username"})
public class CategoryId implements Serializable {
private String name;
#Enumerated(EnumType.STRING)
private Type type;
private String username;
}
The following repository
#Repository
public interface EntryRepository extends JpaRepository<Entry, String> {
Optional<Entry> findEntryById(String id);
#Modifying(clearAutomatically = true, flushAutomatically = true)
#Query(value = "UPDATE Entry e SET e.username = :username, e.type = :type, e.category = :category, e.description = :description, e.amount = :amount, e.createdAt = :date, e.lastUpdate = :lastUpdate, e.isDeleted = :isDeleted WHERE e.id = :id")
void update(#Param("id") String id,
#Param("username") User username,
#Param("type") Entry.Type type,
#Param("category") Category category,
#Param("description") String description,
#Param("amount") Double amount,
#Param("date") Date date,
#Param("lastUpdate") Timestamp lastUpdate,
#Param("isDeleted") Boolean isDeleted);
}
And finally the following Unit Test
#Test
void update() {
//given
User testUser = userRepository.save(new User("testUser#test.com","000000000000000000000000000000000000000000000000000000000000"));
Category testCategory = categoryRepository.save(new Category("Test Category", Entry.Type.Income,testUser));
Entry testEntry = new Entry("testEntry",testUser, Entry.Type.Income,
testCategory, "test",
0.0, new Date(343), from(now()), false);
System.out.println(testCategory);
entryRepositoryUnderTest.save(testEntry);
//when
entryRepositoryUnderTest.update("testEntry",testUser,Expense,testCategory,"testUpdated",1.0,new Date(346), from(now()),true);
Optional<Entry> actual = entryRepositoryUnderTest.findEntryById("testEntry");
System.out.println(actual.get().getCategory());
//then
assertThat(actual.get().getUsername()).isEqualTo(testUser);
assertThat(actual.get().getType()).isEqualTo(Expense);
assertThat(actual.get().getCategory()).isEqualTo(testCategory);
assertThat(actual.get().getDescription()).isEqualTo("testUpdated");
assertThat(actual.get().getAmount()).isEqualTo(1.0);
assertThat(actual.get().getIsDeleted()).isEqualTo(true);
}
When I run the test it fails and I get the following error message:
could not execute update query; SQL [update entry set username_username=?, type=?,category_name=?=category_type=?, description=?, amount=?, created_at=?, last_update=?, is_deleted=? where id=?]; nested exception is org.hibernate.exception.DataException: could not execute update query
As you can see here when SpringBoot is trying to produce a SQL query statement from my #Query parameter it can not properly extract the Category field from the parameters and inject it's composite embeddable key into the SQL statement. It has no problem extracting the User parameter because the User is an entity with an id that is not composite.
Is this a SpringBoot bug or am I missing something?
EDIT:
This is the structure of the database

converting URI to entity with custom controller in spring data rest?

i have an jpa entity like this.
#Entity
#Table(name = "location")
#Data
public class Location {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "LOCATION_ID", unique = true)
#NotEmpty(message = "Please Enter Location ID")
private String name;
#Column(name = "LOCATION_DESCRIPTION")
#NotEmpty(message = "Please Enter Location Description")
private String description;
#ManyToOne
#NotNull(message = "Please Choose a Building")
Building building;
#Version
Long version;
}
and the repository like this.
public interface LocationRepository extends PagingAndSortingRepository<Location, Long> {
Location findByName(#Param("name") String name);
}
i am using spring data rest i am able to create location with rest api by providing the following payload
{
"name":"adminxxxxx","description":"adminxxx" , "building": "http://localhost:8080/buildings/2"
}
now i am trying to write my custom controller which will persist the entity. this is my custom controller
#ExposesResourceFor(Location.class)
#RepositoryRestController
#BasePathAwareController
public class LocationController {
#Autowired
LocationRepository locationDao;
#Autowired
LocationResourceAssembler resourceAssembler;
#Value("${buildings.error.messages.uniqueconstraintviolation}")
String uniqueConstrainMessage;
static final String TAG = LocationController.class.getSimpleName();
#RequestMapping(value="locations",method = org.springframework.web.bind.annotation.RequestMethod.POST)
public ResponseEntity<?> save(#RequestBody #Valid Location location) {
try {
location = locationDao.save(location);
LocationResource b = resourceAssembler.toResource(location);
return ResponseEntity.ok().body(b);
} catch (DataIntegrityViolationException e) {
if (locationAlreadyExists(location.getName()))
throw new LocationAlreadyExistException(uniqueConstrainMessage, location);
else
throw new RuntimeException("Some Error Occured");
}
}
i am getting this error
exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.alamdar.model.Building: no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/buildings/2')
at [Source: java.io.PushbackInputStream#5d468b16; line: 3, column: 60] (through reference chain: com.alamdar.model.Location["building"])</div></body></html>
can anyone please help?
I am not sure why you are writing a custom controller however the issue would appear to be that you do not have a default no args constructor so Jackson cannot instantiate an instance.
This is because you are using Lombok's #Data annotation:
https://projectlombok.org/features/Data.html
You should also annotate you class with #NoArgsConstructor to have a default no-args constructor generated:
#Entity
#Table(name = "location")
#Data
#NoArgsConstructor
public class Location {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "LOCATION_ID", unique = true)
#NotEmpty(message = "Please Enter Location ID")
private String name;
#Column(name = "LOCATION_DESCRIPTION")
#NotEmpty(message = "Please Enter Location Description")
private String description;
#ManyToOne
#NotNull(message = "Please Choose a Building")
Building building;
#Version
Long version;
}

Resources