Проблема с интеграционным тестом Spring, включающим объект с отложенными коллекциями

#spring-test #spring-mvc-test #spring-security-test

#spring-test #spring-mvc-test #spring-security-test

Вопрос:

Я экспериментирую с новой платформой тестирования безопасности Spring. У меня есть тест, который включает возврат объекта JPA с отложенными коллекциями.

Следующий тест заканчивается ошибкой, поскольку одна из отложенных коллекций.

 @RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ActiveProfiles({ Profiles.TEST })
@ContextConfiguration(classes = { FullSecurityTestConfiguration.class, FullIntegrationTestConfiguration.class, BaseTestConfiguration.class, WebMvcConfiguration.class,
        EnableHelperComponents.class })
@TestExecutionListeners(listeners = { ServletTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class, WithSecurityContextTestExcecutionListener.class })
public class CurriculumPermissionEvaluatorAuthorizationTest {

    @Autowired
    private WebApplicationContext wac;
    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @After
    public void clean() {
        SecurityContextHolder.clearContext();
    }
    @Test
    @WithUserDetails("balteo@yahoo.fr")
    public void shouldAllowCurriculumRetrieval() throws Exception {
        mockMvc.perform(get("/curriculum/findCurriculumById").param("id", "1")//
                .contentType(MediaType.APPLICATION_JSON)//
                .header("X-Ajax", "true"))//
                .andDo(print())//
                .andExpect(status().isOk());//
    }
  

Вот сообщение об ошибке:

 21:40:52.731 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction commit
21:40:52.732 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@4a17c31]
21:40:52.733 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@4a17c31] after transaction
21:40:52.733 [main] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
21:40:52.795 [main] DEBUG o.s.w.s.m.m.a.ResponseBodyAdviceChain - Invoking ResponseBodyAdvice chain for body=Curriculum [drivingLicense=true, smoker=false, maxNumberChildren=2, workExperienceInYears=11, dateOfBirth=1975-01-06 00:00:00.0, telephoneNumber=0142778899, firstName=Juliette, visible=true, validated=true]
21:40:52.796 [main] DEBUG o.s.w.s.m.m.a.ResponseBodyAdviceChain - After ResponseBodyAdvice chain body=Curriculum [drivingLicense=true, smoker=false, maxNumberChildren=2, workExperienceInYears=11, dateOfBirth=1975-01-06 00:00:00.0, telephoneNumber=0142778899, firstName=Juliette, visible=true, validated=true]
21:40:52.819 [main] DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolving exception from handler [public com.bignibou.domain.Curriculum com.bignibou.controller.curriculum.CurriculumController.findCurriculumById(java.lang.Long)]: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: com.bignibou.domain.Curriculum.workExperiences, could not initialize proxy - no Session (through reference chain: com.bignibou.domain.Curriculum["workExperiences"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.bignibou.domain.Curriculum.workExperiences, could not initialize proxy - no Session (through reference chain: com.bignibou.domain.Curriculum["workExperiences"])
21:40:52.820 [main] DEBUG o.s.w.s.m.a.ResponseStatusExceptionResolver - Resolving exception from handler [public com.bignibou.domain.Curriculum com.bignibou.controller.curriculum.CurriculumController.findCurriculumById(java.lang.Long)]: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: com.bignibou.domain.Curriculum.workExperiences, could not initialize proxy - no Session (through reference chain: com.bignibou.domain.Curriculum["workExperiences"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.bignibou.domain.Curriculum.workExperiences, could not initialize proxy - no Session (through reference chain: com.bignibou.domain.Curriculum["workExperiences"])
21:40:52.820 [main] DEBUG o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolving exception from handler [public com.bignibou.domain.Curriculum com.bignibou.controller.curriculum.CurriculumController.findCurriculumById(java.lang.Long)]: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: com.bignibou.domain.Curriculum.workExperiences, could not initialize proxy - no Session (through reference chain: com.bignibou.domain.Curriculum["workExperiences"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.bignibou.domain.Curriculum.workExperiences, could not initialize proxy - no Session (through reference chain: com.bignibou.domain.Curriculum["workExperiences"])
21:40:52.821 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Null ModelAndView returned to DispatcherServlet with name '': assuming HandlerAdapter completed request handling
21:40:52.821 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Successfully completed request
  

Что я получаю неправильно в моей тестовой настройке? Почему JPA entity manager закрывается слишком рано? Как я могу обойти эту проблему?

правка 1:

Метод из контроллера:

 @RequestMapping(value = "/findCurriculumById", method = RequestMethod.GET, produces = "application/json")
    @ResponseBody
    public Curriculum findCurriculumById(@RequestParam Long id) {
        return curriculumService.findCurriculum(id);
    }
  

Метод из сервиса:

 @Override
    @PreAuthorize("isAuthenticated() AND hasPermission(#curriculumId, 'curriculumByIdOwnerPermission')")
    @Cacheable(value = CacheConfiguration.DATABASE_CACHE_NAME, key = "'curriculum.Id:'   #curriculumId")
    public Curriculum findCurriculum(Long curriculumId) {
        return curriculumRepository.findOne(curriculumId);
    }
  

правка 2:

Я понял, что тест на самом деле ошибочный, хотя я прокомментировал метод тестирования с помощью @Transactional следующим образом:

 @Test
@WithUserDetails("balteo@yahoo.fr")
@Transactional
public void shouldAllowCurriculumRetrieval() throws Exception {
    mockMvc.perform(get("/curriculum/findCurriculumById").param("id", "1")//
            .contentType(MediaType.APPLICATION_JSON)//
            .header("X-Ajax", "true"))//
            .andDo(print())//
            .andExpect(status().isOk());//
}
  

Вот сообщение об ошибке:

 16:54:31.747 [main] DEBUG o.s.s.access.vote.AffirmativeBased - Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter@ab4a4ae, returned: 1
16:54:31.747 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Authorization successful
16:54:31.747 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - RunAsManager did not change Authentication object
16:54:31.752 [main] DEBUG o.s.w.s.m.m.a.ResponseBodyAdviceChain - Invoking ResponseBodyAdvice chain for body=Curriculum [drivingLicense=true, smoker=false, maxNumberChildren=2, workExperienceInYears=11, dateOfBirth=1975-01-06 00:00:00.0, telephoneNumber=0142778899, firstName=Juliette, visible=true, validated=true]
16:54:31.752 [main] DEBUG o.s.w.s.m.m.a.ResponseBodyAdviceChain - After ResponseBodyAdvice chain body=Curriculum [drivingLicense=true, smoker=false, maxNumberChildren=2, workExperienceInYears=11, dateOfBirth=1975-01-06 00:00:00.0, telephoneNumber=0142778899, firstName=Juliette, visible=true, validated=true]
16:54:31.767 [main] DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolving exception from handler [public com.bignibou.domain.Curriculum com.bignibou.controller.curriculum.CurriculumController.findCurriculumById(java.lang.Long)]: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: com.bignibou.domain.Curriculum.workExperiences, could not initialize proxy - no Session (through reference chain: com.bignibou.domain.Curriculum["workExperiences"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.bignibou.domain.Curriculum.workExperiences, could not initialize proxy - no Session (through reference chain: com.bignibou.domain.Curriculum["workExperiences"])
16:54:31.768 [main] DEBUG o.s.w.s.m.a.ResponseStatusExceptionResolver - Resolving exception from handler [public com.bignibou.domain.Curriculum com.bignibou.controller.curriculum.CurriculumController.findCurriculumById(java.lang.Long)]: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: com.bignibou.domain.Curriculum.workExperiences, could not initialize proxy - no Session (through reference chain: com.bignibou.domain.Curriculum["workExperiences"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.bignibou.domain.Curriculum.workExperiences, could not initialize proxy - no Session (through reference chain: com.bignibou.domain.Curriculum["workExperiences"])
16:54:31.768 [main] DEBUG o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolving exception from handler [public com.bignibou.domain.Curriculum com.bignibou.controller.curriculum.CurriculumController.findCurriculumById(java.lang.Long)]: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: com.bignibou.domain.Curriculum.workExperiences, could not initialize proxy - no Session (through reference chain: com.bignibou.domain.Curriculum["workExperiences"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.bignibou.domain.Curriculum.workExperiences, could not initialize proxy - no Session (through reference chain: com.bignibou.domain.Curriculum["workExperiences"])
16:54:31.768 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Null ModelAndView returned to DispatcherServlet with name '': assuming HandlerAdapter completed request handling
16:54:31.768 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Successfully completed request

MockHttpServletRequest:
         HTTP Method = GET
         Request URI = /curriculum/findCurriculumById
          Parameters = {id=[1]}
             Headers = {Content-Type=[application/json], X-Ajax=[true]}

             Handler:
                Type = com.bignibou.controller.curriculum.CurriculumController
              Method = public com.bignibou.domain.Curriculum com.bignibou.controller.curriculum.CurriculumController.findCurriculumById(java.lang.Long)

               Async:
   Was async started = false
        Async result = null

  Resolved Exception:
                Type = org.springframework.http.converter.HttpMessageNotWritableException

        ModelAndView:
           View name = null
                View = null
               Model = null

            FlashMap:

MockHttpServletResponse:
              Status = 500
       Error message = null
             Headers = {Content-Type=[application/json]}
        Content type = application/json
                Body = 
       Forwarded URL = null
      Redirected URL = null
             Cookies = []
16:54:31.770 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction rollback
16:54:31.770 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Rolling back JPA transaction on EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@76d0bc1d]
16:54:31.771 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@76d0bc1d] after transaction
16:54:31.771 [main] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
16:54:31.771 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Resuming suspended transaction after completion of inner transaction
16:54:31.771 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Creating new transaction with name [com.bignibouX.tests.security.curriculum.CurriculumPermissionEvaluatorAuthorizationTest.clean]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
16:54:31.771 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Opened new EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@3972d402] for JPA transaction
16:54:31.771 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Exposing JPA transaction as JDBC transaction [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@21bcecc5]
16:54:31.771 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction commit
16:54:31.772 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@3972d402]
16:54:31.772 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@3972d402] after transaction
  

правка 3:

Вот соответствующая часть учебного плана:

 @Entity
public class Curriculum {

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "curriculum")
    private Set<WorkExperience> workExperiences;
  

Ответ №1:

Ваш метод тестирования не транзакционный.

Вам нужно аннотировать либо CurriculumPermissionEvaluatorAuthorizationTest , либо shouldAllowCurriculumRetrieval() с @Transactional помощью.

Комментарии:

1. Сэм: Мне очень жаль: я только что понял, что аннотирование метода с помощью @Transactional на самом деле не решило проблему… Моя ошибка в том, что я изначально пометил ответ как принятый…

2. Как выглядит конфигурация транзакции для curriculumService.findCurriculum(id) ? Аннотирован ли метод, класс или интерфейс с помощью @Transactional , и если да, то на какое propagation значение они установлены?

3. findCurriculum @Transactional соответствует следующему объявлению Roo ITD: declare @type: CurriculumServiceImpl: @Transactional; Распространение должно быть по умолчанию, т. Е. обязательным.

4. Насколько я вижу, ваш метод тестирования все равно должен быть @Transactional ; в противном случае ваша транзакция (и, следовательно, ваш сеанс) завершается, когда завершается вызов curriculumService.findCurriculum(id) .

5. Если вы не можете заставить его работать, убедившись, что тест является транзакционным , было бы проще помочь вам, если вы создадите уменьшенный тестовый проект , который демонстрирует проблему.