Issue
I have a Spring MVC application (Spring Boot v. 1.2.5) that uses JPA to interact with a popular Sql Database.
Thus, I have several entities mapping all the tables in the db. Clearly, these classes are having only getters/setters and annotations for the relations between entities.
E.g.:
@Entity
@Table
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonView(View.Private.class)
private Long id;
@Column(nullable = false)
@JsonView(View.Public.class)
private String name;
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "categoryId", nullable = false, updatable = false)
@JsonIgnore
private Category category;
//Constructors Getters and Setters
...
}
My question is: should I unit test these classes? What should I test? How
Solution
I advice you to test everything you write (or you choice to write)... so in this case I see the following elements :
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false)
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "categoryId", nullable = false, updatable = false)
You define some behavior with this annotation (I only pick those from JPA, the same thing should be done with the JSONView annotation) and you want to follow by unit testing if everything keep working fine (like you defined).
@Test(expect = SQLException.class)
public void should_not_allow_null_name() {
/* Given */ Article article = new Article(null, new Category());
/* When */ articleRepository.save(article);
}
With this kind of (simple) test, you can follow if the behavior respect what you have implemented. If someone (maybe yourself) remove this annotation, you will have an alert.
But don't test the default behavior (for example, the column name, you choose to let JPA (and the ORM) choose for you... So don't test the framework, it's the limit.
On the HOW to test it, I like to use (since Spring Boot) the project called DBSetup. It allows me to have my dataset hard-coded in my test instead of verbose XML. It's a very interesting project from the ninja-squade.
An example of test :
Database Test Config :
@Configuration
@EnableJpaRepositories(basePackages = "lan.dk.podcastserver.repository")
@EntityScan(basePackages = "lan.dk.podcastserver.entity")
public class DatabaseConfiguraitonTest {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@Autowired
public FullTextEntityManager fullTextEntityManager(EntityManager entityManager) {
return getFullTextEntityManager(entityManager);
}
public static final DateTimeFormatter formatter = new DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE).appendLiteral(" ").append(DateTimeFormatter.ISO_LOCAL_TIME).toFormatter();
public static final Operation DELETE_ALL_PODCASTS = deleteAllFrom("PODCAST");
public static final Operation DELETE_ALL_ITEMS = deleteAllFrom("ITEM");
public static final Operation DELETE_ALL_TAGS = sequenceOf(deleteAllFrom("PODCAST_TAG"), deleteAllFrom("TAG"));
public static final Operation DELETE_ALL = sequenceOf(DELETE_ALL_ITEMS, DELETE_ALL_TAGS, DELETE_ALL_PODCASTS, DELETE_ALL_TAGS);
}
==> https://gist.github.com/davinkevin/bb4f62aaec031b68b8f3
And test :
@Transactional
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {DatabaseConfiguraitonTest.class, HibernateJpaAutoConfiguration.class})
public class ItemRepositoryTest {
@Autowired DataSource dataSource;
@Autowired ItemRepository itemRepository;
private final static DbSetupTracker dbSetupTracker = new DbSetupTracker();
public static final Operation INSERT_REFERENCE_DATA = sequenceOf(
insertInto("PODCAST")
.columns("ID", "TITLE", "URL", "TYPE", "HAS_TO_BE_DELETED")
.values(1, "AppLoad", null, "RSS", false)
.values(2, "Geek Inc HD", "http://fake.url.com/rss", "YOUTUBE", true)
.build(),
insertInto("ITEM")
.columns("ID", "TITLE", "URL", "PODCAST_ID", "STATUS", "PUBDATE", "DOWNLOADDDATE")
.values(1L, "Appload 1", "http://fakeurl.com/appload.1.mp3", 1, Status.FINISH, now().minusDays(15).format(formatter), now().minusDays(15).format(formatter))
.values(2L, "Appload 2", "http://fakeurl.com/appload.2.mp3", 1, null, now().minusDays(30).format(formatter), null)
.values(3L, "Appload 3", "http://fakeurl.com/appload.3.mp3", 1, Status.NOT_DOWNLOADED, now().format(formatter), null)
.values(4L, "Geek INC 123", "http://fakeurl.com/geekinc.123.mp3", 2, Status.DELETED, now().minusYears(1).format(formatter), now().format(formatter))
.values(5L, "Geek INC 124", "http://fakeurl.com/geekinc.124.mp3", 2, Status.FINISH, now().minusDays(15).format(formatter), now().minusDays(15).format(formatter))
.build(),
insertInto("TAG")
.columns("ID", "NAME")
.values(1L, "French Spin")
.values(2L, "Studio Knowhere")
.build(),
insertInto("PODCAST_TAG")
.columns("PODCAST_ID", "TAG_ID")
.values(1, 1)
.values(2, 2)
.build()
);
@Before
public void prepare() throws Exception {
Operation operation = sequenceOf(DELETE_ALL, INSERT_REFERENCE_DATA);
DbSetup dbSetup = new DbSetup(new DataSourceDestination(dataSource), operation);
dbSetupTracker.launchIfNecessary(dbSetup);
}
@Test
public void should_find_by_podcast_and_page() {
/* Given */
dbSetupTracker.skipNextLaunch();
Integer podcastId = 1;
PageRequest pageRequest = new PageRequest(1, 1, Sort.Direction.ASC, "id");
/* When */
Page<Item> itemByPodcast = itemRepository.findByPodcast(podcastId, pageRequest);
/* Then */
PageAssert
.assertThat(itemByPodcast)
.hasSize(1)
.hasTotalElements(3)
.hasTotalPages(3)
.hasNumberOfElements(1);
ItemAssert
.assertThat(itemByPodcast.getContent().get(0))
.hasTitle("Appload 2");
}
==> https://gist.github.com/davinkevin/df041729608dc21bf7f3
Answered By - Kevin Davin