2

I have two tests files in project. One is testing directly my persistence layer :

@RunWith(SpringRunner.class)
@DataJpaTest
public class UserTests {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository repository;

    @Test
    public void whenFindByEmail_thenReturnUser() {
        // given
        User user = new User("email@email.com", "12345678", "Some Name");
        entityManager.persist(user);
        entityManager.flush();

        // when
        User found = repository.findByEmail(user.getEmail());

        // then
        assertThat(found.getName()).isEqualTo(user.getName());
        assertThat(found.getEmail()).isEqualTo(user.getEmail());
        assertThat(found.getPasswordHash()).isEqualTo(user.getPasswordHash());
    }
}

The other one is testing a service using the persistence layer :

@RunWith(SpringRunner.class)
@DataJpaTest
public class UserServiceTests {

    @Autowired
    private UserService service;

    @Test
    public void testSuccessfullUserCreation() {
        UserCreationResult res = service.createUser("anything@anything.com", "1234567890", "Test");
        assertThat(res).isEqualTo(UserCreationResult.OK);
    }

    @Test
    public void testWrongEmailUserCreation() {
        UserCreationResult res = service.createUser("anything@anything", "1234567890", "Test");
        assertThat(res).isEqualTo(UserCreationResult.INVALID_EMAIL);
    }

    @Test
    public void testTooShortPasswordUserCreation() {
        String shortPassword =
                String.join("", Collections.nCopies(UserService.minPasswordLength - 1, "0"));
        UserCreationResult res = service.createUser("anything@anything.com", shortPassword, "Test");
        assertThat(res).isEqualTo(UserCreationResult.WRONG_PASSWORD_LENGTH);
    }

    @Test
    public void testTooLongPasswordUserCreation() {
        String longPassword =
                String.join("", Collections.nCopies(UserService.maxPasswordLength + 1, "0"));
        UserCreationResult res = service.createUser("anything@anything.com", longPassword, "Test");
        assertThat(res).isEqualTo(UserCreationResult.WRONG_PASSWORD_LENGTH);
    }

    @Test
    public void testMaxLengthPasswordUserCreation() {
        String maxPassword =
                String.join("", Collections.nCopies(UserService.maxPasswordLength, "0"));
        UserCreationResult res = service.createUser("anything@anything.com", maxPassword, "Test");
        assertThat(res).isEqualTo(UserCreationResult.OK);
    }

    @Test
    public void testMinLengthPasswordUserCreation() {
        String minPassword =
                String.join("", Collections.nCopies(UserService.minPasswordLength, "0"));
        UserCreationResult res = service.createUser("anything@anything.com", minPassword, "Test");
        assertThat(res).isEqualTo(UserCreationResult.OK);
    }

    @Test
    public void testReservedEmailUserCreation() {
        String email = "email@email.com";
        UserCreationResult res = service.createUser(email, "1234567890", "Test");
        assertThat(res).isEqualTo(UserCreationResult.OK);
        res = service.createUser(email, "1234567890", "Test");
        assertThat(res).isEqualTo(UserCreationResult.RESERVED_EMAIL);
    }
}

First, my service autowiring wasn't working (UnsatisfiedDependencyException) so I had to add : @ComponentScan("my.service.package") annotation to the test class.

This made the tests of UserServiceTests work when they were run independently (using eclipse to run only this class).

But when running all the tests of my app (in eclipse or with a good old mvn clean test), I had the same error on the same test class.

I tried adding the same component scan annotation to the other test class (UserTests) then everything worked.

I removed the component scan annotation from UserServiceTests and it still works.

I obviously deduced that the order the tests are executed matters.

Here are my 3 questions, the real one being the last one :

  1. In the first place why do I have to put this component scan annotation even if my class is properly annotated @Service (so should be detected as a bean) ?
  2. How is it that the order of tests matters ?
  3. How can I have multiple JPA tests files that would run with a proper dependency injection independently ?

Here is my service class :

@Service
public class UserService {

    @Autowired
    private UserRepository repository;

    public static final int minPasswordLength = 8;
    public static final int maxPasswordLength = 50;

    public static enum UserCreationResult {
        OK, INVALID_EMAIL, RESERVED_EMAIL, WRONG_PASSWORD_LENGTH, UNKNOWN_ERROR
    }

    @Transactional
    public UserCreationResult createUser(String email, String password, String name) {
        if (password.length() < minPasswordLength || password.length() > maxPasswordLength) {
            return UserCreationResult.WRONG_PASSWORD_LENGTH;
        }
        if (!EmailValidator.getInstance().isValid(email)) {
            return UserCreationResult.INVALID_EMAIL;
        }
        final User existingUser = repository.findByEmail(email);
        if (existingUser != null) {
            return UserCreationResult.RESERVED_EMAIL;
        }
        final User user = repository.save(new User(email, password, name));
        return user == null ? UserCreationResult.UNKNOWN_ERROR : UserCreationResult.OK;
    }
}

And there is my pom.xml :

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.somedomain</groupId>
    <artifactId>ws-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>My App</name>
    <description>My app's description</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>commons-validator</groupId>
            <artifactId>commons-validator</artifactId>
            <version>1.6</version>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>
Pierre Mardon
  • 727
  • 8
  • 25

1 Answers1

2

Thanks to this question and answer I was able to make my tests work with proper configuration.

In my UserServiceTests, the service was basically not autowired because that's the expected behavior of @DataJpaTest : it doesn't scan for regular beans.

So I used @SpringBootTest for this class and removed the component scanning in both test classes.

After that, some of my service tests were failing because with @SpringBootTest, the database is not reset after each test.

I added a simple repository cleaning after each service test and everything works fine.

This question still remains :

How is it that the order of tests matters ?

Here are my new test classes :

Service tests :

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTests {

    @Autowired
    private UserService service;

    @Autowired
    private UserRepository repository;

    @After
    public void cleanUsers() {
        repository.deleteAll();
    }

    @Test
    public void testSuccessfullUserCreation() {
        UserCreationResult res = service.createUser("anything@anything.com", "1234567890", "Test");
        assertThat(res).isEqualTo(UserCreationResult.OK);
    }

    @Test
    public void testWrongEmailUserCreation() {
        UserCreationResult res = service.createUser("anything@anything", "1234567890", "Test");
        assertThat(res).isEqualTo(UserCreationResult.INVALID_EMAIL);
    }

    @Test
    public void testTooShortPasswordUserCreation() {
        String shortPassword =
                String.join("", Collections.nCopies(UserService.minPasswordLength - 1, "0"));
        UserCreationResult res = service.createUser("anything@anything.com", shortPassword, "Test");
        assertThat(res).isEqualTo(UserCreationResult.WRONG_PASSWORD_LENGTH);
    }

    @Test
    public void testTooLongPasswordUserCreation() {
        String longPassword =
                String.join("", Collections.nCopies(UserService.maxPasswordLength + 1, "0"));
        UserCreationResult res = service.createUser("anything@anything.com", longPassword, "Test");
        assertThat(res).isEqualTo(UserCreationResult.WRONG_PASSWORD_LENGTH);
    }

    @Test
    public void testMaxLengthPasswordUserCreation() {
        String maxPassword =
                String.join("", Collections.nCopies(UserService.maxPasswordLength, "0"));
        UserCreationResult res = service.createUser("anything@anything.com", maxPassword, "Test");
        assertThat(res).isEqualTo(UserCreationResult.OK);
    }

    @Test
    public void testMinLengthPasswordUserCreation() {
        String minPassword =
                String.join("", Collections.nCopies(UserService.minPasswordLength, "0"));
        UserCreationResult res = service.createUser("anything@anything.com", minPassword, "Test");
        assertThat(res).isEqualTo(UserCreationResult.OK);
    }

    @Test
    public void testReservedEmailUserCreation() {
        String email = "email@email.com";
        UserCreationResult res = service.createUser(email, "1234567890", "Test");
        assertThat(res).isEqualTo(UserCreationResult.OK);
        res = service.createUser(email, "1234567890", "Test");
        assertThat(res).isEqualTo(UserCreationResult.RESERVED_EMAIL);
    }
}

JPA tests :

@RunWith(SpringRunner.class)
@DataJpaTest
public class UserTests {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository repository;

    @Test
    public void whenFindByEmail_thenReturnUser() {
        // given
        User user = new User("user@user.com", "12345678", "Some Name");
        entityManager.persist(user);
        entityManager.flush();

        // when
        User found = repository.findByEmail(user.getEmail());

        // then
        assertThat(found.getName()).isEqualTo(user.getName());
        assertThat(found.getEmail()).isEqualTo(user.getEmail());
        assertThat(found.getPasswordHash()).isEqualTo(user.getPasswordHash());
    }
}
Pierre Mardon
  • 727
  • 8
  • 25