Testing Spring Boot

  • Testing is done using Mockito Junit
    • Mocking and Spying using Mockito
    • Testing Restful Services with Spring Boot and Mockito.
  • Frameworks for Unit Test Cases
    • Mockito
    • Spring Boot Test Framework
  • Layer based Testing Frameworks
    • For Web Layer
      • Spring Mock MVC Framework
    • For Business Layer
      • Using Mockito
    • For Data Layer
      • Using Data JPA Test.
  • One way to test sample data is to create data stubs of entities.
    • Create a class with methods which contains methods which return different data structures of an entity.
    • One function In class returns empty entity, another with Empty list of entity, Another with unique entity and another with list with single entity.
    • Maintaining stubs Becomes difficult over time as data increases.
    • Mocking helps us to overcome this problem.
    • With mocks we can programmatically create classes.
  • Create a mock of service class
    • Create stubs for function responses using when.
    • Assert the response 
    • Implement all methods.
    • Check null returns.
    • Check for number of Calls to methods.
    • Spy the repository responses.
  • Mock versus Spy
    • Mocking creates a mock of the class all Calls I made on the mock of the class.Spy uses the real class.
    • Null pointer exceptions will come wherever applicable in spy in mock we get null values in response 
      • For example, if we mock a list then while reading empty list We will get Null values. If we spy a list, then while reading empty list we will get Null pointer exception.
    • Use spy where you want to use the original dependency for example, spy repositories.
    • We can call verify on spy.
    • When we don’t have access to a class and want to check what is going on in a Class, we use spy.
    • https://github.com/gauravmatta/springmvc/blob/master/Unittesting/src/test/java/com/springimplant/unittesting/services/impl/ListMockTest.java
  • Good Unit test cases
    • Readable
      • One look at test and we know what is being tested.
      • 30 sec benchmark should be there
    • Fast
      • Since a unit test runs repeatedly, it should be fast.
    • Isolated
      • Fails only when there is an issue with Code.
    • Should run often
  • Testing with Junit
    • Use assertEquals To compare two objects.
      • Returns false if the objects differ.
    • We may need to manually get and set values returned from dependencies as we can’t mock responses.
    • We may need to create multiple stubs of service class and interfaces to inject data into tests.
    • Keeping and maintaining stubs Is difficult as they need to be updated. Each time service changes are there.
    • For junit 4 to execute a set of statements Before each test case create a method annotated by @Before
    • For Junit 5 the annotation is @BeforeEach
  • Testing with Mockito
    • Used to mock results from dependencies injected such as Services or repositories.
    • We can programmatically create classes with mocks.
    • We can mock data service methods i.e we can create our assumed responses on mocking of methods.
    • We  can mock a class using mock keyword As follows.
      • SomeDataService serviceMock = mock(SomeDataService.class).
    • We can mock a method/function using when keyword as follows.
      • when(serviceMock.returnAllValues()).thenReturn(new int[] {1,2,3};
    • Instead of using static mock method to mock the classes We can also use annotation @InjectMocks or @Mock to create mocks.
    • If we want to inject mocks via annotation we should use @RunWith(MockitoJunitRunner.class) annotation above class.
    • @InjectMocks creates an Instance, class and injects the mocks that are Created with @Mock Annotation into this instance.
    • We can return multiple/different values from the mock  
      • when(mock.size()).thenReturn(5).thenReturn(10).
      • When First time we call or assert mock.size() It returns 5 Next time when we assert or Call, it returns 10.
    • We can also return with parameters like
      • When (mock.get(0)).thenReturn(“Spring Implant”)
      • assertEquals(“Spring Implant”,mock.get(0)).
      • assertEquals(null,mock.get(1)).
    • We can also return with Generic parameters using Mockito For example
      • when(mock.get(anyInt()).thenReturn(“Spring Implant”)
      • assertEquals(“Spring Implant”,mock.get(0)).
    • There are many other “any” Methods or argument marcher methods present in Mockito class.
      • The Mockito class extends Argument Matchers class.
    • We can also verify method Calls as follows
      • String value = mock.get(0) is call to method.
      • verify(mock).get(0)
        • Check if get(0) is called in method at least once
      • verify(mock).get(anyInt())
        • Checks if get() with any int Param is called at least once.
      • Verify(mock,times(1)).get(anyInt())
        • Verify if mock.get(anyInt()) with any integer parameter is called at most once.
      • Similarly we have verification like
        • verify(mock,atMost(2)).get(anyInt()).
        • Verify(mock,never()).get(2)
        • Verify(mock,atLeastOnce()).get(anyInt())
    • We can also capture arguments. For example, we have a method call as “mock.add(“Spring Implant”)
      • We use ArgumentCaptor<String> To Capture the arguments for example
        • ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class)
        • Verify(mock).add(captor.capture());
        • assertEquals(“Spring Implant”,captor.getValue());
    • We can capture Arguments on multiple calls as well such as
      • mock.add("Item1");
      • mock.add("Item2");
      • ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
      • verify(mock,times(2)).add(captor.capture());
      • List<String> allValues = captor.getAllValues();
      • assertEquals("Item1",allValues.get(0));
      • assertEquals("Item2",allValues.get(1));
    • We can also use spy instead of mock if we want to check Real class behaviour. 
      • In spy Original behaviour of class is retained.
      • Only things we override our overridden by the characteristics that we specify.
      • Whenever we want to use original class values, we use spy such as utility classes.
      • For example
        • ArrayList<String> arrayListSpy = spy(ArrayList.class);
        • arrayListSpy.add("Item0");
        • System.out.println(arrayListSpy.get(0));//Item0
        • System.out.println(arrayListSpy.size());//1
        • arrayListSpy.add("Item");
        • arrayListSpy.add("Item1");
        • System.out.println(arrayListSpy.size());//3
        • when(arrayListSpy.size()).thenReturn(5);
        • System.out.println(arrayListSpy.size());//5
        • arrayListSpy.add("Item2");
        • System.out.println(arrayListSpy.size());//5
        • verify(arrayListSpy).add("Item2");
  • Unit testing with spring boot and Mockito
    • Controller Testing
      • For Junit 4 Annotate the class with @RunWith(SpringRunner.class)
      • Annotate the class with @WebMvcTest(HelloWorldController.class)
      • Mock all services.
      • Autowire MockMvc object in class
        • Using MockMVC perform() method assert The response in a MVC Result object using andExpect() method.
        • MvcResult result = mockMvc.perform(request)
          .andExpect(status().isOk())
          .andExpect(content().json("[{id: 3,name:Wickets,price:400,quantity:10},{id: 4,name:Bails,price:100,quantity:10}]"))
          .andExpect(content().contentType(MediaType.APPLICATION_JSON))
      • We test the endpoint, as follows.
        • Check status is 200 or is OK.
        • Check for content type and data.
        • Check for Json content Using Content().json(jsonString)
        • We can also use jsonAssert.assert Equals(expected,result.getResponse, Boolean string structure validation check).
        • json.content internally uses jsonAssert.assertEquals with false json string structure validation check.
      • Use MockBean to Mock all Beans.
      • Stub all service methods using when() and thenReturn().
      • https://github.com/gauravmatta/springmvc/blob/master/Unittesting/src/test/java/com/springimplant/unittesting/controller/HelloWorldControllerTest.java
    • Using in memory database
      • We should not use default database for unit test. If the database is down, we will have build failures.
      • It is always better to use H2 database, which is in memory database.
      • The following properties
        • spring.jpa.show-sql =true
        • spring.h2.console.enabled = true
    • Writing test for business service
      • InjectMock The service or service implementation Used by method and method is a part of.
      • Mock the repository.
      • Stub the repository methods.
      • Using assert methods Perform validations.
    • Writing test for Data Layer
      • We only need to write test cases for our methods in Repository.
      • We test the repository Methods.
      • Annotate the class with @DataJPATest
        • Load spring context.
      • Annotate with @RunWith(SpringRunner.class) For junit4
      • Autowire the repository.
      • Write queries in file “data.sql” In resources folder To populate data in the database.
      • Always use in memory database.
    • Writing integration tests
      • Use annotation @SpringBootTest
        • Launches the entire spring boot application.
      • Add parameter webEnvironment= WebEnvirnoment.RANDOM_PORT.
      • Autowire TestRestTemplate.
        • Use getForObject Method of the TestRestTemplate Class to Get String response end Point.
      • Use Assert or JSONAssert to assert the response.
    • We should use @MockBean To mock out dependencies, we do not wish to talk to.
      • These may include Services/components which talk to external interfaces Like downstream systems or file systems, etc.
      • We should never talk to real database, all external components that our system depends upon.
    • Creating different test configuration
      • Place a configuration properties file under src/test/resources.
      • Annotate the test class with annotation @TestPropertySource(location={“classpath: testconfiguration.properties”})
        • This helps us to use a particular configuration file for a test case.
    • Writing unit tests For other request methods
      • There are many request methods
        • GET
        • POST
        • PUT
        • DELETE
      • There are variety of response status
        • 200 - SUCCESS
        • 404-RESOURCE NOT FOUND
        • 400-BAD REQUEST
        • 201-CREATED
        • 401-UNAUTHORIZED
        • 500-SERVER ERROR
      • We can use Post method of  MockMvcRequestBuilder To create a RequestBuilder object.
      • We also need to provide request body content during the post method. We can provide that through content() method Of the RequestBuilder class.
      • We can then execute the above RequestBuilder object using MockMVC perform method.
      • We can check the status from the MockMvc expect() method as follows
        • mockmvc.expect(status.isok()).
  • Assertion Frameworks
    • There are three frameworks which help us to write asserts.
      • Hamcrest Matchers
      • JsonAssert
      • JsonPath
    • Spring Boot starter Test library gets them all.
  • Assertions with Hamcrest
  • Assertions with AssertJ
  • Assertions using JSONPath
  • Measuring  Test coverage
    • Right click on project ->coverage as -> junit Test
    • Analyses which lines in our source code are not covered by our unit test.
    • A coverage report is a measure of Number of lines executed as a part of our unit test.
    • Basically, all services, repositories and controller should be covered.
    • Red lines in eclipse are not covered while green lines are covered.
    • Coverage is not 100% measure of eliminating defects alone.
      • Coverage should go along with Assets.
    • Our unit test should run as fast as possible as they run each time our build runs.
  • Recommendations

No comments:

Post a Comment

Spring Boot

What is circular/cyclic dependency in spring boot? When two services are interdependent on each other, that is to start one service, we requ...