3

I have Spring Boot project with test that does not fail (but should).

Am I doing something wrong or it is a issue with Spring?

For an small example I have created a small project with 2 entities (User and Category) and one Controller class that has DELETE method (https://github.com/sk8ter/demo).

Category entity has an id of User entity without cascade option, so it should fail while deleting a User that has category:

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue
    private long id;
    private String name;
    ...
}

@Entity
@Table(name = "category")
public class Category {

    @Id
    @GeneratedValue
    private long id;
    private String name;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
    ...
}

Controller is also is pretty simple:

@RestController
@RequestMapping(value = "/users", produces = "application/json;charset=UTF-8")
public class UserCategory {

    @Autowired
    private UserRepository userRepository;

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(value = "/{id}", method = DELETE, consumes =   MediaType.ALL_VALUE)
    public void deleteCategory(@PathVariable Long id) {
        User user = userRepository.getOne(id);
        userRepository.delete(user);
    }
}

And finally a test:

@Transactional
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = DemoApplication.class)
public class DemoApplicationTests {

    @Autowired
    protected WebApplicationContext context;

    protected MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
    }

    @Test
    public void testName() throws Exception {
        mockMvc.perform(delete("/users/1"))
                .andExpect(status().isOk());

//      EntityManagerFactory entityManagerFactory = context.getBean(EntityManagerFactory.class);
//      SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
//      sessionFactory.getCurrentSession().flush();
    }
}

Test will fail in case I remove @Transactional annotation from DemoApplicationTests, but in this case changes will be committed to a DB.

Commented 3 lines in the test does not help either.

  • Are you sure that you have category where foreign key points to your user? – Oskar Dajnowicz Nov 25 '15 at 00:21
  • Yes, how else I could distinguish categories of certain user. – Viktor Kostov Nov 25 '15 at 09:30
  • You should have a ManyToMany table(category_id, user_id), otherwise you will have to duplicate same categories for different users but this is not the clue, i ask that are you sure that your has db already created category entity witch is assigned to certain user? because if you do the db would not allow you to delete the user without cascade option set on table, otherwise if you are just deleting a user wich is not assigned to category then the test will pass because you are not abusing the foreign key rule – Oskar Dajnowicz Nov 25 '15 at 09:37
  • This is a good point, but categories are unique for each user and I don't need to share categories. It is like Todo application, where each user creates own categories for notes. In case I delete user, I want to delete associated categories (cascade), but the problem is that test did not show this issue (since I don't have cascade option). So it was found by another tester. In the future, I assume, there will be more tests that passes, but fail in production, because test does not flush changes. – Viktor Kostov Nov 25 '15 at 09:52
  • Hibernate/JPA doesn't check if entity is assigned somewhere by foreign key, it just create appropirate sql queries (without cascade option, it just doesnt create the cascading sql's), the database nextly checks if you are not violating some rules and if you do then it throws an exception wich is caught in your code, what kind of DB are you using? Can you describe what happens to db when you try to delete user? – Oskar Dajnowicz Nov 25 '15 at 10:19
  • I understand this, and I want this exception to be in my test, so that I can fix it before I deploy to dev/prod server. What is a point of test, if it does not work? MySQL, for testing H2. – Viktor Kostov Nov 25 '15 at 10:27

1 Answers1

3

I did not want to annotate method or class with @Rollback(false), since I wanted all my tests be idempotent. In case I annotate @Rollback(false), H2 also fails with foreign key's constraints.

I found a solution:

@Transactional
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = DemoApplication.class)
public class DemoApplicationTests {

    @Autowired
    protected WebApplicationContext context;

    protected MockMvc mockMvc;

    @PersistenceContext
    EntityManager em;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
    }

    @Test
    public void testName() throws Exception {
        mockMvc.perform(delete("/users/1"))
                .andExpect(status().isOk());

        em.flush();
    }
}

Key lines:

@PersistenceContext
EntityManager em;
...
// Manual flush is required to avoid false positive in test
em.flush();

So SessionFactory does not work from Spring Documentation

// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush();
  • Solution seems ok but the SessionFactory doesn't work because you use JPA wich wraps it in entity manager and you should rewrite your test because you are actually checking if the status is OK and it is 200 after that you make em.flush() wich causes exception – Oskar Dajnowicz Nov 25 '15 at 15:44
  • What do you propose? Could you make an example? – Viktor Kostov Nov 26 '15 at 15:26