26

In JUnit tests with Spring MockMVC, there are two methods for authenticating as a Spring Security user: @WithMockUser creates a dummy user with the provided credentials, @WithUserDetails takes a user's name and resolves it to the correct custom UserDetails implementation with a custom UserDetailsService (the UserDetailsServiceImpl).

In my case, the UserDetailsService loads an user from the database. The user I want to use was inserted in the @Before method of the test suite.

However, my UserDetailsServiceImpl does not find the user.

In my @Before, I insert the user like this:

User u = new User();
u.setEMail("test@test.de");
u = userRepository.save(u);

And in the UserDetailsServiceImpl:

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = this.userRepository.findOneByEMail(username);

    if (user == null)
        throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
    return user;
}

How can I use an account created in @Before with @WithUserDetails?

elaforma
  • 652
  • 1
  • 9
  • 29
  • This StackOverflow answer is pretty much instructive : [Spring Test & Security: How to mock authentication?](https://stackoverflow.com/a/43920932/2971820) – jpmottin Apr 19 '21 at 12:46

4 Answers4

10

Unfortunately, you can't do easily @WithUserDetails with @Before, because Spring @WithUserDetails annotation will invoke Spring security context test listener before running setUp method with @Before.

Here is https://stackoverflow.com/a/38282258/1814524 a little trick and answer to your question.

naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
hya
  • 1,708
  • 2
  • 15
  • 22
  • Heads up: if you use transactions you will need to manually remove the content with `@AfterTransaction`. They are not rolled back automatically like with `@Before`. – NicolaeS Dec 16 '20 at 15:07
  • 1
    2021 UPDATE: With `setupBefore = TEST_EXECUTION` it works for me. See my answer. – Newbie Jan 10 '22 at 17:43
9

You can use @PostConstruct instead of @Before. That did the trick for me. Can anybody confirm that?

gofabian
  • 263
  • 3
  • 6
7

Use setupBefore = TEST_EXECUTION

This will effectively cause @WithUserDetails to execute after @Before and @BeforeEach

@BeforeEach
public void createUser() {
   ...
}

@Test
@WithUserDetails(value = "username", setupBefore = TestExecutionEvent.TEST_EXECUTION)
public void expectToWork() {
   ...
}
Newbie
  • 4,462
  • 11
  • 23
6
@Inject
private EntityManager em;

@Inject
PlatformTransactionManager txManager;

@BeforeTransaction
public void setup() {
    new TransactionTemplate(txManager).execute(status -> {

        User u = new User();
        u.setEMail("test@test.de");
        em.save(u);

        return null;
    });
}

@AfterTransaction
public void cleanup() {
    new TransactionTemplate(txManager).execute(status -> {
        // Check if the entity is managed by EntityManager.
        // If not, make it managed with merge() and remove it.
        em.remove(em.contains(u) ? user1 : em.merge(u));
        return null;
    });
}


@Test
@Transactional
@WithUserDetails(value = "test@test.de", userDetailsServiceBeanName = "loadUserByUsername")
public void test() {

}
Michal Foksa
  • 11,225
  • 9
  • 50
  • 68