Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue with @WithUserDetails and spring boot 1.4 TestEntityManager

I have an issue with Spring Boot's TestEntityManager and @WithUserDetails annotation.

Here is my test suite:

public class AdvertisementAuthorizationTest extends AbstractIntegrationTest {

    private static final String IMPERSONATOR_EMAIL = "[email protected]";

    private final static String OWNER_OF_ADVERTISEMENT_EMAIL = "[email protected]";

    @Autowired
    private TestEntityManager testEntityManager;

    @Autowired
    private MockMvc mockMvc;

    private Advertisement advertisement;

    private UserAccount impersonator;

    private ObjectMapper mapper = new ObjectMapper();

    @Before
    public void setUp() {
        advertisement = testEntityManager.persist(createAdvertisement(OWNER_OF_ADVERTISEMENT_EMAIL));
        impersonator = testEntityManager.persist(createUserAccount(IMPERSONATOR_EMAIL));
    }

    @Test
    @WithUserDetails(IMPERSONATOR_EMAIL)
    public void shouldNotAllowAdvertisementModification() throws Exception {
        String jsonAdvertisement = mapper.writeValueAsString(advertisement);
        mockMvc.perform(put("/api/advertisement/{id}", advertisement.getId())//
                .contentType(MediaType.APPLICATION_JSON)//
                .content(jsonAdvertisement))//
                .andDo(print())//
                .andExpect(status().isForbidden());//
    }

    @Test
    @WithUserDetails(OWNER_OF_ADVERTISEMENT_EMAIL)
    public void shouldAllowAdvertisementModification() throws Exception {
        String jsonAdvertisement = mapper.writeValueAsString(advertisement);
        mockMvc.perform(put("/api/advertisement/{id}", advertisement.getId())//
                .contentType(MediaType.APPLICATION_JSON)//
                .content(jsonAdvertisement))//
                .andDo(print())//
                .andExpect(status().isOk());//
    }

}

Here is the super class:

@AutoConfigureMockMvc
@AutoConfigureTestEntityManager
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {TestApplication.class})
@WebAppConfiguration
@ActiveProfiles(Profiles.TEST)
@Transactional
public abstract class AbstractIntegrationTest {

}

When I run the tests it seems the entities are not persisted to H2 by TestEntityManager as indicated by this error message:

Hibernate: select useraccoun0_.id as id1_9_, useraccoun0_.address_id as address13_9_, useraccoun0_.email as email2_9_, useraccoun0_.email_notification as email_no3_9_, useraccoun0_.enabled as enabled4_9_, useraccoun0_.first_name as first_na5_9_, useraccoun0_.last_connection_date as last_con6_9_, useraccoun0_.password as password7_9_, useraccoun0_.registration_date as registra8_9_, useraccoun0_.role as role9_9_, useraccoun0_.token as token10_9_, useraccoun0_.user_account_type as user_ac11_9_, useraccoun0_.version as version12_9_ from user_account useraccoun0_ where useraccoun0_.email=?
22:52:39.943 [Test worker] WARN  o.s.test.context.TestContextManager - Caught exception while allowing TestExecutionListener [org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener@43ec4dcd] to process 'before' execution of test method [public void com.bignibou.it.web.security.advertisement.AdvertisementAuthorizationTest.shouldNotAllowAdvertisementModification() throws java.lang.Exception] for test instance [com.bignibou.it.web.security.advertisement.AdvertisementAuthorizationTest@646496bc]
java.lang.IllegalStateException: Unable to create SecurityContext using @org.springframework.security.test.context.support.WithUserDetails([email protected], userDetailsServiceBeanName=)
    at org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener.createSecurityContext(WithSecurityContextTestExecutionListener.java:79)
    at org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener.beforeTestMethod(WithSecurityContextTestExecutionListener.java:56)
    at org.springframework.test.context.TestContextManager.beforeTestMethod(TestContextManager.java:269)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:112)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:56)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
    at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:109)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:364)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
    at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.security.core.userdetails.UsernameNotFoundException: Username: [email protected] not found!
    at com.bignibou.service.security.UserAccountUserDetailsService.loadUserByUsername(UserAccountUserDetailsService.java:21)
    at org.springframework.security.test.context.support.WithUserDetailsSecurityContextFactory.createSecurityContext(WithUserDetailsSecurityContextFactory.java:56)
    at org.springframework.security.test.context.support.WithUserDetailsSecurityContextFactory.createSecurityContext(WithUserDetailsSecurityContextFactory.java:39)
    at org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener.createSecurityContext(WithSecurityContextTestExecutionListener.java:76)
    ... 43 common frames omitted

Can someone please help?

like image 848
balteo Avatar asked Jul 08 '16 21:07

balteo


1 Answers

This is an issue with timing with regard to TestExecutionListener callbacks and @Before test methods.

@WithUserDetails is supported by the Spring Security's WithSecurityContextTestExecutionListener which will never run after a @Before method. It is therefore impossible for Spring Security to see the user that you persist to the database in your setUp() method. That's basically what the exception is telling you: Spring Security attempted to read the user from the database before it existed.

One way to fix this is to migrate to @Sql support for inserting test data in the database. You might not find that as comfortable as simply persisting your entities, but the @Sql approach allows the test data to be created within the test-managed transaction (i.e., does not require manual clean up). Note that you will have to upgrade to Spring Security 4.1.1 in order for this to work properly.

An alternative way to address this is to persist your entities in a user-managed transaction in a @BeforeTransaction method -- for example, using Spring's TransactionTemplate. However, you will then need to manually clean up the database in an @AfterTransaction method in a similar fashion. Plus, you will still need to upgrade to Spring Security 4.1.1 in order for this to work.

Something like the following should do the trick:

@Autowired
private TestEntityManager testEntityManager;

@Autowired
PlatformTransactionManager transactionManager;

@BeforeTransaction
public void setUp() {
    new TransactionTemplate(transactionManager).execute(status -> {
        UserAccount owner = testEntityManager.persist(createUserAccount(OWNER_OF_ADVERTISEMENT_EMAIL));
        Language language = testEntityManager.persist(createLanguage("Français"));
        DayToTimeSlot dayToTimeSlot = testEntityManager.persist(createDayToTimeSlot());

        advertisement = testEntityManager.persist(createAdvertisement(owner, language, dayToTimeSlot));
        impersonator = testEntityManager.persist(createUserAccount(IMPERSONATOR_EMAIL));

        return null;
    });
}

@AfterTransaction
public void tearDown() {
    new TransactionTemplate(transactionManager).execute(status -> {
        testEntityManager.remove(testEntityManager.find(Advertisement.class, advertisement.getId()));

        UserAccount owner = advertisement.getUserAccount();
        testEntityManager.remove(testEntityManager.find(UserAccount.class, owner.getId()));

        testEntityManager.remove(testEntityManager.find(UserAccount.class, impersonator.getId()));

        return null;
    });
}

Regards,

Sam

like image 196
Sam Brannen Avatar answered Sep 21 '22 17:09

Sam Brannen