18

I have @Entity classes in an external package that also have static metamodels. In my application's service class, I am using those metamodels and the EntityManager/CriteriaBuilder/CriteriaQuery to retrieve my data. This works fine when running the application. However, when running unit tests, my metamodels and their attributes are always null.

Code...

package com.example.core.entities;

@Entity
@Table(schema = "lookup", name="BookingSystem")
public class BookingSystem implements ILookupEntity, IAuditEntity, Serializable {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  @Column(name = "id")
  public Integer id;

  @Column(name = "name")
  public String name;

  @Column(name = "code")
  public Integer code;
}

package com.example.core.entities;

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(BookingSystem.class)
public abstract class BookingSystem_ {
  public static volatile SingularAttribute<BookingSystem, Integer> id;
  public static volatile SingularAttribute<BookingSystem, Integer> code;
  public static volatile SingularAttribute<BookingSystem, String> name;
}

Usage in my app's service class...

package com.example.bookingsystem;

@Service
public class BookingService {
  @PersistenceContext
  private EntityManager entityManager;

public void saveBooking(Booking booking) {
//...
  RepositoryQueryBuilder<BookingSystem> bookingSystemSelector = new RepositoryQueryBuilder<>(entityManager, BookingSystem.class);
  List<BookingSystem> bookingSystems = bookingSystemSelector
      .and(BookingSystem_.code, booking.bookingSystem.code) //<-- Here "BookingSystem_.code" is null.
      .getResultList();
  //...
  }
}

The "RepositoryQueryBuilder" class is just a utility builder class that wraps an EntityManager, CriteriaBuilder, etc. Basically modeled after this example... JPA Criteria Predicate Conditions

Unit test code...

package com.example.bookingsystem;

public abstract class BaseTestSetup {
  @InjectMocks
  protected BookingService bookingService;

  protected EntityManager entityManager = PowerMockito.mock(EntityManager.class);
  protected CriteriaBuilder criteriaBuilder = PowerMockito.mock(CriteriaBuilder.class);
  protected CriteriaQuery<BookingSystem> criteriaQuery = PowerMockito.mock(CriteriaQuery.class);
  protected Root<BookingSystem> root = PowerMockito.mock(Root.class);

  protected void arrange() {
    when(entityManager.getCriteriaBuilder()).thenReturn(criteriaBuilder);
    when(criteriaBuilder.createQuery(BookingSystem.class)).thenReturn(criteriaQuery);
    when(criteriaQuery.from(Matchers.<Class<BookingSystem>>any())).thenReturn(root);
    when(criteriaQuery.from(Matchers.<EntityType<BookingSystem>>any())).thenReturn(root);
}

}

@RunWith(PowerMockRunner.class)
public class BookingServiceTest extends BaseTestSetup {
  @BeforeClass
  @Override
  public void arrange() {
    super.arrange();

    //...
}

@Test
public void doIt() {
    Booking booking = new Booking();
    booking.id = 12345;
    booking.bookingSystem = new BookingSystem();
    booking.bookingSystem.id = 1;
    booking.bookingSystem.code = 106000;

    bookingService.saveBooking(booking);
  }
}

I've looked at this JPA/Hibernate Static Metamodel Attributes not Populated -- NullPointerException, but the solution seems to be "make sure that the entity and its metamodel are in the same package", but as you can see, both are already in my "com.example.core.entities" package.

I'm using all bean and annotation driven configruation in my code (no persistence or context xml files). As far as testing goes, I'm using TestNG and PowerMock from within IntelliJ.

It just seems as if the metamodels aren't being picked up during unit tests. Any ideas.

Community
  • 1
  • 1
HenryC
  • 359
  • 2
  • 13
  • It seems that JPA processor should provide instances for SingularAttribute properties. Did you find any solution for unit testing? – srnjak May 31 '17 at 14:56
  • I having a similiar problem. I am using Statics but without criteria, like: `select o.id, o." + Entity_.attribute.getName() + " FROM Entity o ...` I have 2 test, but 1st one works fine, but the 2nd doesn't recognize the static meta model and throws NPE – FiruzzZ Jun 28 '17 at 13:33

4 Answers4

6

The static metamodel classes are populated when hibernate is loaded. So, either you configure hibernate context in your test or you populate the attributes manually before the method execution. In you code, you could do:

@Test
public void doIt() {
    BookingSystem_.code = new SingularAttributeMock<BookingSystem, Integer>();
    bookingService.saveBooking(booking);
  }
}

The class SingularAttributeMock can be created custom-made in order to use it in your tests. You can also use any other implementation of the SingularAttribute class.

public class SingularAttributeMock<X, Y> implements SingularAttribute<X, Y> {
   //Overriding methods of SingularAttribute...
}
Andrés Cuadros
  • 1,962
  • 1
  • 21
  • 22
5

Instead of creating own class, I suggest making Mockito to do the job for you.

 @Mock // declare a mock along the others you might have
 private SingularAttribute<BookingSystem, Integer> code;

 @Before
 public void setUp() throws Exception { 
     // fill metamodel with it
     BookingSystem_.code = code;
 }

Worth to mention however that bringing metamodels to service layer is not very good practice, you would rather push them down to DAO methods.

no id
  • 1,642
  • 3
  • 24
  • 36
1

There is no need to manual initialization. You should observe following rules :

  • Metamodel classes should declared as abstract class.
  • Metamodel classes should be in the same package as the entity classes they describe;
  • They should have the same name as the entity classes they describe, followed by an underscore (e.g. Product is the entity, Product_ is the metamodel class);
  • If an entity inherits from another entity or from a mapped superclass, its metamodel class should inherit from the metamodel class that describes its immediate superclass (e.g. if SpecialProduct extends Product, which extends PersistentObject, then SpecialProduct_ should extend Product_ which should extend PersistentObject_).
Pasha
  • 1,534
  • 15
  • 27
0

In my case, mock the metamodel doesn't worked, so I just get it from entityManager.

private EntityManager entityManager;

@Before
public void init() {
    // Assume that entityManager was correctly initialized.

    this.configMetaModel();
}
private void configMetaModel() {

   Metamodel metamodel = this.entityManager.getMetamodel();

   BiFunction<EntityType<MY_ENTITY>, String, Attribute>
                bfAttr = (entity, field) -> entity.getAttributes()
                .stream()
                .filter(a -> a.getName().equals(field))
                .findAny()
                .get();

   Function<Attribute, SingularAttribute<MY_ENTITY, String>> fToStr = attribute ->
            (SingularAttribute<MY_ENTITY, String>) attribute;

   Function<Attribute, SingularAttribute<MY_ENTITY, LocalDate>> fToDate = attribute ->
            (SingularAttribute<MY_ENTITY, LocalDate>) attribute;

   EntityType<MY_ENTITY> entity = metamodel.entity(MY_ENTITY.class);
   MY_ENTITY_.id = fToStr.apply(bfAttr.apply(entity, "id"));
   MY_ENTITY_.name = fToStr.apply(bfAttr.apply(entity, "name"));
   MY_ENTITY_.someDate = fToDate.apply(bfAttr.apply(entity, "someDate"));
}

"MY_ENTITY" replace my entity

"MY_ENTITY_" replace my entity metamodel

Once I did this, I could run all my unit test perfectly.