Reputation: 13666
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
Upvotes: 7
Views: 28847
Reputation: 976
On domain entities, it's clear that you should test every coded behaviour. Testing domain entities behaviour wouldn't be different from unit testing whatever type of function/method. Often you declare a test suite for each aggregate root and you check that invariants are correctly applied and mutations happen in the expected way. At this level, you are totally unaware about persistence
In 99.9% of cases, domain entities are also persisent entities, this means that are stored to/rehydrated from some durable datastore, and you would use some framework to address the ORM/ODM problem, such as JPA+Spring Data+Hibernate. Usually these frameworks implement API that decorate your domain elements with metadata that tell how they have to be represented into datastores (is it a good practice?). Since the whole application behaviour depends on this declarations, there are many cases where it worths to have tests
It's clear that testing how entities are represented in a DB doesn't make sense if you don't have a DB under your bottom, so these tests fall into integration tests. We tend to think of integration test as something that tests the entire application stack, but why we could not have domain-level integration tests?
let's suppose I have
public interface UserRepository extends CrudRepository<User, String> {
@Query("select * from users where birthdate=?1")
public List<User> findUsersByBirthdate(Date birthdate);
}
if I'm just using userRepository.findById(), already implemented from Spring Data, I just could rely on 3rd party code and don't have doubts about its correctness, but now I'm introducing a custom behaviour that deserves a test
@Test
public void given_user_when_find_by_different_birthdate_should_return_empty_list () {
repository.save(new User(BirthdatesFixture.1ST_JANUARY));
assertThat(repository.findUsersByBirthdate(BirthdatesFixture.1ST_FEUBRARY)).isEmpty()
}
Generally speaking, I like to create an integration testcase for each repository to check whether my critical custom declarations are correct. In some other cases (especially with mapping annotations) it would be enough to rely on green "e2e integration tests". We should always remember that we have to add tests only when there is no other test that asserts something about the newly added code (and TDD helps with that)
Upvotes: 2
Reputation: 593
JUnit test for JPA entity with code coverage
public class ArticleTest {
public Article crateTestSuite(){
return new Article ();
}
@Test
public void testGetId() {
Long id= 0;
Xyz xyz =null;
xyz = crateTestSuite();
id = xyz.getId()
}
@Test
public void setId(Integer id) {
Long id= 0;
Xyz xyz =null;
xyz = crateTestSuite();
xyz.setId(id)
}
}
Upvotes: -4
Reputation: 531
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
Upvotes: 9
Reputation: 2363
You should test functionality not class. If you are not sure if mapping is working correctly, then maybe testing save/load of objects of this class is suitable test for you. However, unit testing should also isolate persistence layer, so you can test your business logic instead of persistence layer.
Upvotes: 3