Types of automated testing
In general, automated tests fall into one of the following
categories, from fastest to slowest:
Unit tests:
These typically test a single method, class, or function in isolation,
providing assurance to the developer that their code operates as designed. For
many reasons, including the need to keep our tests fast and stateless, unit
tests often “stub out” databases and other external dependencies (e.g.,
functions are modified to return static, predefined values, instead of calling
the real database).
Acceptance tests: Humble and Farley define the
difference between unit and acceptance testing as, “The aim of a unit test is
to show that a single part of the application does what the programmer intends
it to....The objective of acceptance tests is to prove that our application
does what the customer meant it to, not that it works the way its programmers
think it should.”
After a build passes our unit tests, our deployment pipeline
runs it against our acceptance tests. Any build that passes our acceptance
tests is then typically made available for manual testing (e.g., exploratory
testing, UI testing, etc.) as well as for integration testing.
Integration tests: Integration tests are where we
ensure that our application correctly interacts with other production
applications and services, as opposed to calling stubbed out interfaces.
Kim, Gene. The DevOps Handbook: . IT Revolution Press
We will
cover Unit testing using JUNIT5 in this post
Download Junit5 from below link
Unit Testing Using Junit5:
JUnit 5 is the
next generation of JUnit. The goal is to create an up-to-date foundation for
developer-side testing on the JVM.
Useful Annotations And Classes
@BeforeEach
|
This method in test class will be executed before each test case and
can be used to prepare data needed to run the test cases.
|
@Test
|
This annotation will mark a test method as a test case.
|
@Mock
|
This annotation is used to create mock for Services,Repositories
etc.
|
@ExtendWith(MockitoExtension.class)
|
This method initiates all mocks
|
MockMvc
|
Mock MVC is utility by spring which helps us test the controllers
by initiating HTTP requests.
|
org.mockito.when (Static)
|
This static method is used to create mock interections, such as
when a specific method of a service is called. An example will be shown
later.
|
org.mockito.then
|
This static method is used with the when operation. See the coding
example below.
|
org.mockito.verify
|
This static method is used to verify that during the execution of
test how many times a particular method has been called e.t.c.
|
Best Practice for writing
test Using BDD Technique
· Write Test
cases using the below pattern
o Given : A
Data Set
o When : When a
function is called
o Then : what
should happen / returned
Examples Of JUNIT5 Test Cases for Controller and Services
are given below :
Controller Get Method to Test
@RequestMapping("/recipe/{id}/show")
public String showRecipe(@PathVariable String id, Model model){
System.out.println("showRecipe called");
model.addAttribute("recipe",recipeService.getRecipeById(new Long(id)));
return "recipe/show";
}
Below test case has been written using Given,When,Then method.
Notice
· Under Given:
we are preparing the data and mock interaction
· under when :
We are calling the Controller service which we want to test.
· Under Then :
we are checking the conditions to verify if the test has been passed or not
Test
@Test
void testGetRecipe() throws Exception {
//Given
Recipe recipe=new Recipe();
recipe.setId(1L);
MockMvc mockMvc= MockMvcBuilders.standaloneSetup(recipeController).build();
when(recipeService.getRecipeById(anyLong())).thenReturn(recipe);
//When
mockMvc.perform(
get("/recipe/1/show"))
//Then
.andExpect(status().isOk())
.andExpect(view().name("recipe/show"))
.andExpect(model().attributeExists("recipe"));
}
Controller Post method to test
Controller Post Method
@PostMapping("/new")
public String processCreationForm(@ModelAttribute @Valid Owner owner,Model
model,BindingResult bindingResult){
Owner ownerSaved=ownerService.save(owner);
model.addAttribute("owner",ownerSaved);
return "redirect:/owners/" + ownerSaved.getId();
}
Test
@Test
void processCreationForm() throws Exception {
//Given
Owner owner=Owner.builder().id(1L).build();
when(ownerService.save(any())).thenReturn(owner);
//When
mockMvc.perform(post("/owners/new"))
//Then
.andExpect(status().is3xxRedirection())
.andExpect(model().attributeExists("owner"))
.andExpect(view().name("redirect:/owners/1"));
}
Service Method to Test
Service Method
public void deleteRecipeById(Long recipeId){
log.debug(">>>
RecipeServiceImpl.deleteRecipeById method is called");
recipeRepository.deleteById(recipeId);
}
Test
@Test
void testDeleteRecipeById(){
//Given
Long idToBeDeleted=1L;
//When , since the delete method
returns void we cant have when here but
// verify that the delete method was
actually called
recipeService.deleteRecipeById(idToBeDeleted);
//Then
verify(recipeRepository,times(1)).deleteById(any());
}
Encourage TDD development in
your organization
I have found TDD to be the best
way to develop applications as it gives us a prospective of writing the test first
and than write the code to pass it. We think like a user first and than coder
which gives us a lot of flexibility. Additionally , TDD is the best way to have
test coverage for your code as writing tests afterwords is a tedious and boring
job for developers. Moreover, having test cases helps you quickly/automatically
test your already written code while you are adding new functionality and exponentially reduces the time
spent testing manually.
Advantage in CI/CD
Going forward to CI/CD you will
be ready to get your code automatically tested using all these test cases you
have been writing using JUNIT5 and your future builds will be more reliable and
less error pron.
SpringBoot Project on github
utilizing Junit5
You can also fork my below github
Spring boot project to see Junit5 in action.