1

I'm creating an app for practice in java spring boot, but I faced with the problem when I started to test my UserService. I don't know why, but UserRepository just doesn't want to work. It initialized but when I try to call the save() method, it doesn't save anything in database.

So here's my test:

UserServiceTest.java

@SpringBootTest
@ExtendWith(SpringExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class UserServiceTest {

    @InjectMocks
    private UserService userService;

    @Spy
    private UserRepo userRepo;

    @Spy
    private ValidationService validationService;
    @Spy
    private PasswordEncoder passwordEncoder;


    @AfterEach
    public void deleteDb() {
        List<UserEntity> users = userRepo.findAll();
        userRepo.deleteAll(users);
    }

    @BeforeEach
    public void setupMock() {
        Mockito.when(userRepo.save(Mockito.any(UserEntity.class))).thenReturn(new UserEntity());
    }

    @Test
    @Order(1)
    public void testUserAdding() {

        List<UserAddModel> addModels = getUserAddModels();
        addModels.forEach(m -> {
            userService.addUser(m);
            List<UserEntity> users = userRepo.findAll();
            System.out.println(users);
            UserEntity user = userRepo.findByPhoneNumber(m.getPhoneNumber()).orElseThrow(UserNotFoundException::new);
            isUserGetModelValid(m, UserGetModel.toModel(user));
        });
        addModels = getInvalidUserAddModels();
        addModels.forEach(m -> Assertions.assertThrows(InvalidDataException.class,
                () -> isUserGetModelValid(m, userService.addUser(m))));
    }


    private List<UserAddModel> getUserAddModels() {
        return new ArrayList<>(List.of(
                new UserAddModel(
                        "Alex",
                        "Filtch",
                        null,
                        "+19772395747",
                        null,
                        "AbobaAbobaAboba"
                ),
                new UserAddModel(
                        "Scarlet",
                        "Brown",
                        "FiftithEnumed",
                        "+19164167722",
                        null,
                        "Fi123sdamJfw+"
                ),
                new UserAddModel(
                        "Andy",
                        "Balk",
                        "Afton",
                        "+112486759123",
                        "IBiB@Basas",
                        "IBiB@Basas"
                )
        ));

    }

    private void isUserGetModelValid(UserAddModel userAddModel, UserGetModel userGetModel) {
        Assertions.assertAll(
                () -> Assertions.assertNotNull(userGetModel.getId()),
                () -> Assertions.assertEquals(userAddModel.getFirstName(), userGetModel.getFirstName()),
                () -> Assertions.assertEquals(userAddModel.getLastName(), userGetModel.getLastName()),
                () -> Assertions.assertEquals(userAddModel.getPatronymic(), userGetModel.getPatronymic()),
                () -> Assertions.assertEquals(userAddModel.getEmail(), userGetModel.getEmail()),
                () -> Assertions.assertEquals(userAddModel.getPhoneNumber(), userGetModel.getPhoneNumber())
        );
    }
}

And here I decided to check if userRepo is working right in UserService.java:

UserEntity us = userRepo.save(user);

But us variable has null fields after saving:

enter image description here

So what can be the problem? If you know, please tell me, I'd really appreciate it!

Sergey Tsypanov
  • 3,265
  • 3
  • 8
  • 34
Cross
  • 497
  • 1
  • 3
  • 13
  • 1
    Which is exactly what you told it to do... What do you think the line in your `setupMock` method does... You tell it here that when `save` is called return an empty/new `UserEntity`. – M. Deinum Aug 04 '23 at 07:31
  • @M.Deinum Oh, alright, thanks, I got it. I just thought that the ```@Spy``` annotation will make the repository to do the real behavior of repository object. – Cross Aug 04 '23 at 07:37
  • It does, until you register behavior on it, then it no longer does. – M. Deinum Aug 04 '23 at 07:46
  • @M.Deinum Well, I tried to remove ```setupMock``` method and now ```us``` variable is just null. Even without fields. – Cross Aug 04 '23 at 07:50
  • Which is the default behavior for a mock. You are using Spring Boot so you should be using `@SpyBean` not `@Spy`. Looking at your test class you posted here wouldn't even work. All fields would be `null` in your test and the test would fail with `NullPointerException`. – M. Deinum Aug 04 '23 at 07:53
  • @M.Deinum Alright, thanks, you're right. Now when I replaced my ```@Spy``` annotations with ```@SpyBean``` it really fails with ```NullPointerException```. Could you please help me: should I return new UserEntity with all set fields(in ```initializeMock``` method), or there is a way to use real repo, but with mock database(H2, for example)? – Cross Aug 04 '23 at 08:03
  • It should fail when using `@Spy` not `@SpyBean`. Your test is confusing what is it you want to test? The repository only then use `@DataJpaTest` (assuming this is a JPA based repository) instead of `@SpringBootTest`. I don't get the mocking and the spying nor see the benefit in this test (neither of mocking/spying nor the `@SpringBootTest`). – M. Deinum Aug 04 '23 at 08:46

1 Answers1

0

First of all you don't need @ExtendWith(SpringExtension.class) over you test class as @SpringBootTest already contains it. Second, to have actual data saved into the database you should use real beans, not mocks, as well as have some real storage. Just add this into your application-test.yml:

spring:
  datasource:
    url: jdbc:h2:mem:~/test;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:
    driver-class-name: org.h2.Driver

This will give you in-memory database for tests. Then use @Autowired instead of @Spy/@Mock:

@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Autowired
    private UserRepo userRepo;

    @MockBean
    private ValidationService validationService;
    @MockBean
    private PasswordEncoder passwordEncoder;


    @AfterEach
    public void deleteDb() {
        userRepo.deleteAll();
    }

This would allow you to have real userService and real userRepo and mock validationService and passwordEncoder

Sergey Tsypanov
  • 3,265
  • 3
  • 8
  • 34
  • Thank you for answering. Yes, firstly I tried the way you proposed, but I got exception ```org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement Sequence "USERS_SEQ" not found; SQL statement``` And I started to look for solutions and decided to use @Spy as I thought it will imitate the real behaviour of repository. So could you please help me please with this exception? – Cross Aug 04 '23 at 09:49
  • The exception is thrown because the sequence you are using for generation of entity ids is not created. If you use Liquibase or another DB migration tool please configure tests to run schema migration before starting tests. If you don't then copy your DB schma into a separate SQL file and run it before tests. See https://www.baeldung.com/java-h2-automatically-create-schemas or https://stackoverflow.com/questions/5225700/can-i-have-h2-autocreate-a-schema-in-an-in-memory-database – Sergey Tsypanov Aug 04 '23 at 10:38
  • @Cross please join https://chat.stackoverflow.com/rooms/254815/userrepository-doesnt-work-in-spring-boot-tests – Sergey Tsypanov Aug 05 '23 at 12:23
  • Unfortunately I still get this error, even after I initialized the scheme through the script and through flyway :/ @Sergey Tsypanov – Cross Aug 05 '23 at 13:56