0

I have written unit tests for my class and they seem to run fine. But the challenge is I can't get full coverage or get the builder.and(...) operation part to execute or be covered.

Any idea what could be going on? Thank you

This is the utility class to create Predicates


import static java.util.Objects.nonNull;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.persistence.criteria.Predicate;
import lombok.experimental.UtilityClass;
import org.springframework.data.jpa.domain.Specification;

@UtilityClass
public class UsersSearchSpecificationFactory {

  public static Specification<User> createSpecification(UsersSearchCriteria criteria) {
    final List<Specification<User>> specificationList = new ArrayList<>();
    if (nonNull(criteria.getAge()))
      specificationList.add(ageSpecification(criteria.getAge()));

    if (nonNull(criteria.getAccountType()))
      specificationList.add(accountTypeSpecification(criteria.getAccountType()));

    if (nonNull(criteria.getAge())) {
      specificationList.add(idSpecification(criteria.getId()));
    }

    return (root, query, builder) -> {
      return builder.and(
          specificationList.stream()
              .map(specification -> specification.toPredicate(root, query, builder))
              .toArray(Predicate[]::new)
      );
    };
  }

  private static Specification<User> ageSpecification(String value) {
    return (root, query, builder) -> builder.isMember(value, root.get("age"));
  }

  private static Specification<User> accountTypeSpecification(String value) {
    return (root, query, builder) -> builder.equal(root.get("accountType"), value);
  }

  private static Specification<User> idSpecification(UUID value) {
    return (root, query, builder) -> builder.equal(root.get("id"), value);
  }
}

This is the Repository interface:


    import java.util.UUID;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
    import org.springframework.data.repository.CrudRepository;
    
    interface UserRepository extends CrudRepository<User, UUID>, JpaSpecificationExecutor<User> {
    
      Page<User> findAll(UsersSearchCriteria criteria, Orderable orderable, PageRequest pageable);
    }

The Spock Specification logic is in here


    import javax.persistence.EntityManager
    import javax.persistence.PersistenceContext
    import org.springframework.beans.factory.annotation.Autowired
    import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
    import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
    import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
    
    @DataJpaTest
    @AutoConfigureTestDatabase
    class DatabaseSpecification extends InternalSpecification{
    
      @Autowired
      private TestEntityManager tem
    
      @PersistenceContext
      private EntityManager em
    
    
      protected <T> T persist(T entity) {
        final persisted = tem.persistAndFlush(entity)
        tem.detach(persisted)
        return persisted
      }
    }

This is my test:

  

    import static java.util.UUID.randomUUID
    
    import org.springframework.beans.factory.annotation.Autowired
    import org.springframework.data.domain.PageRequest
    
    class UserRepositoryTest extends DatabaseSpecification {

    def "should_find_user"() {
        given:
        userRepository = Mock(UserRepository)
    
        and:
        final user = Mock(User) as Specification
        final criteria = new UserSearchCriteria(
            filter: 'userFilter',
            id: 'id'
        )
        final orderable = new orderable()
        final pageable = new pageable()
    
    
        when:
        final foundUser = userRepositoryImpl.findAll(criteria, orderable, pageable)
    
        then:
        1 * userRepositoryImpl.repository.findAll(_ as Specification, _ as Pageable) >> new PageImpl<>([user])
    
        and:
        foundUser.content.get(0) == user
      }
    }

Denis Kisina
  • 350
  • 4
  • 19
  • Try debugging to see if the code is actually called. If it is, then it might be a limitation of Jacoco, see https://www.eclemma.org/jacoco/trunk/doc/counters.html for more information. Also this line looks suspect `final user = Mock(User) as Specification` what are you trying to achieve here.# – Leonard Brünings Dec 31 '21 at 17:21
  • I ran a debug, the moment it gets to ``` return (root, query, builder) -> ``` it does not get in. And the Mock part, I was trying to force a User to be of type Specification because the method returns ```Specification``` – Denis Kisina Dec 31 '21 at 17:50
  • 2
    You can't cast from `User` to `Specification` that is not how generics work. Also be sure to use the correct `Specification`, i.e. use type alias or fully qualified class name, to avoid conflicts with `spock.lang.Specification`. Also, mocking `UserRepository` looks suspect, you'll probably want to use a `@DataJpaTest` and inject the repository. – Leonard Brünings Dec 31 '21 at 18:41
  • @LeonardBrünings Thanks!! I changed the was I was creating ```Specification``` and how I was mocking the repository and used ```@DataJpaTest```. Now the class is 100% covered! Thank you – Denis Kisina Dec 31 '21 at 19:18
  • You can answer your own question with the solution you came up with. – Leonard Brünings Dec 31 '21 at 21:25
  • This question is a good example of why it is such a bad idea to post code snippets only instead of full classes including package names and imports: A Spock `spock.lang.Specification` is not the same as a JPA `org.springframework.data.jpa.domain.Specification`. That immediately jumped out at me when reading your question for the first time just now. Leonard pointed that out already. I think, if you still use the JPA specification class anywhere in your working solution, you should explicitly show that in your answer instead of hiding it behind `// omitted` comments. Others could learn from it. – kriegaex Jan 01 '22 at 02:51
  • @kriegaex thank you for pointing that out. I have tried to add some imports. – Denis Kisina Jan 01 '22 at 03:12
  • 1
    I was referring to your question, where it would have been most helpful for others to spot that you import two different `Specification` classes. But also in your edited answer, the imports are incomplete, no `Specification` there, not even the Spock one. So now I am not sure again whether you still use `org.springframework.data.jpa.domain.Specification` in your test or not. Please learn what an [MCVE](https://stackoverflow.com/help/mcve) is and why information hiding is such a bad idea if you want other people's debugging support. – kriegaex Jan 01 '22 at 03:23
  • Next time, I will most certainly prepare an MCVE. Because currently I just extent or import most functionality. – Denis Kisina Jan 01 '22 at 03:32
  • Thanks for the new edit. Now I see that you still **import** both `Specification` classes. The way you are doing this suggests that the first import will be ignored and the second one wins. With the test code still being incomplete, readers still cannot see if you actually **use** the JPA specification class or not. You did not answer that question either. If you do use it, I have further comments for you. You are making it really hard to help you. Wouldn't it be much easier to share your full test than to edit it 5 times, adding bits and pieces, but still keeping it opaque and incomplete? – kriegaex Jan 01 '22 at 03:42
  • Yes I use ```org.springframework.data.jpa.domain.Specification ```. I do realize that this sample code does not explain well my architecture. But the ```Specification``` classes can not conflict because they are in two different classes and I just import or extend to use their functionality. I tried to put everything together to catch the solution as best as I could. Next time I will prepare an MVCE. – Denis Kisina Jan 01 '22 at 05:35
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/240629/discussion-between-chabo-and-kriegaex). – Denis Kisina Jan 01 '22 at 14:46

1 Answers1

1

Thank you @LeonardBrünings for the guidance.

So, the mistakes I was making were two:

  1. I was mocking the repository the wrong way instead of using @DataJpaTest and then injecting the repository.
  2. I was creating my generic class wrong

The code below worked with 100% coverage, of course, bits have been omitted

   import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
   import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
   import javax.persistence.EntityManager
   import javax.persistence.PersistenceContext
   import org.springframework.beans.factory.annotation.Autowired
   import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
   import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
   import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
   import org.springframework.data.jpa.domain.Specification
   import org.springframework.core.env.Environment
   import spock.lang.Specification


    @DatabaseTests
    @DataJpaTest
    class UserRepositoryTest extends Specification {

      @Autowired
      private TestEntityManager testEntityManager
    
      @Autowired
      private UserRepository userRepository
    
      def "should_find paged_users_using_search_criteria"() {
        given:
        final id = randomUUID()
    
        and:
        final orderable = new Orderable()
        final pageable = new PageableFor()
        final user = new UserId('an user')
      
        final organization = persistAndDetach(new Organization(id: id, name: 'organization'))
        final user = persistAndDetach(new User(
        ....
        // omitted
        ....
        ))
    
        final criteria = new SearchCriteria(
        ....
        // omitted
         ....
        )
    
        when:
        final foundPage = userRepository.findAll(criteria, orderable, pageable)
    
        then:
        foundPage.content[0]== user
      }
    }

     protected <T> T persistAndDetach(T entity) {
        final persisted = testEntityManager.persistAndFlush(entity)
        testEntityManager.detach(persisted)
        return persisted
      }

Denis Kisina
  • 350
  • 4
  • 19