0

I have no test experience and try to test a method by a Unit Test. All the examples that I have a look at perform operations via uses mock values. I know, I will also use mock values with mockito that I use in my project. Here is my service method that I want to test:

ProductServiceImpl:

public List<ProductDTO> findAllByCategoryUuid(UUID categoryUuid) {

    // code omitted

    return result;
}

Here is my Unit Test class:

ProductServiceImplTest:

// ? @InjectMocks
@Autowired 
ProductServiceImpl productService;

@Mock
ProductRepository productRepository;
  

@Test
public void testFindAllByCategoryUuid() {

    UUID categoryUuid = UUID.randomUUID();

    final List<Product> productList = new ArrayList<>();
    for (int i = 0; i < size; i++) {
        // create product by setting "categoryUuid" and add to productList
    }
    productRepository.saveAll(productList);


    when(productService.findAllByCategoryUuid(categoryUuid)
                .thenReturn(productList);
}

My questions:

1. Is the approach above is correct in order to test the service method? I think I should not deal with inside the service method and just pass categoryUuid and check the result of that method for testing? Is that true?

2. In test class, I used @Autowired to access service method, but I am not sure if I should @Mock. Is there any mistake?

Any help would be appreciated.


Update: I also create unit test using DiffBlue plugin and it generates a test method as shown below. But I think it seems to be as testing repository methods rather than the service method. Is not it?

@Test
public void testFindAllByCategoryUuid() {
    when(this.productRepository.findAllByCategoryUuid((UUID) any()))
        .thenReturn(new ArrayList<Product>());
    assertTrue(this.productService.findAllByCategoryUuid(UUID.randomUUID())
        .isEmpty());
    verify(this.productRepository).findAllByCategoryUuid((UUID) any());
    // ...
}
  • Please read: [Can I ask only one question per post?](https://meta.stackexchange.com/questions/222735/can-i-ask-only-one-question-per-post) – Turing85 Jun 27 '21 at 18:52
  • my comment about testFindAllByCategoryUuid - it's using a mock productRepository to test productService (productService is under test because it's inside the assert statement), looks valid for me – shikida Jun 27 '21 at 18:53
  • ideally, JUnit tests should have been written BEFORE the actual code, so it works as a spec for developers, as far as I remember from the TDD book. Nowadays, people seem to be happier when they have a high test coverage, but I am not sure if all code needs all these tests. CRUD tests sounds like a waste of time for me, IMHO – shikida Jun 27 '21 at 18:56
  • You seem to have a misunderstanding what a mock is. A mock must be configured to return something. The test generated by DiffBlue does actually sets up the mock to return a known value (an empty list in this case), but I do not understand what the call to `assertTrue(...)` and `verify(...)` are supposed to test. – Turing85 Jun 27 '21 at 18:56
  • @justice I might answer the question if it is well-formed. – Turing85 Jun 27 '21 at 18:57
  • @shikida Thanks for reply, but I am confused which question you replied. Could you please give a detailed info by providing question number? Thanks in advance. –  Jun 27 '21 at 18:58
  • sorry, I was talking about the UPDATE section – shikida Jun 27 '21 at 19:04
  • @Turing85 Thanks for reply, yes I have no experience and really too confused after reading many examples and videos. But, if I want to test a service method, why do I deal with inside it? I think I just need to prepare mock values and pass to that service method. And then, compare the returned values (via `thenReturn`) with the prepared mock values. Is that true? –  Jun 27 '21 at 19:16
  • @shikida What about #1 and #2 ? –  Jun 27 '21 at 19:16

3 Answers3

1

I am not an expert but I will try to answer you question

The general approach to unit test your method should be to test the output against all possible set of inputs. in your specific case you can test

  • input: existing UUID output : NonNull List.
  • input: non existing UUID output : Empty List.
  • input: null : Empty List.

Now what you have done here is right you need to Autowire the class that you are writing test cases for and mock the dependencies in that class. Only thing wrong is

when(productService.findAllByCategoryUuid(categoryUuid)
                .thenReturn(productList);

should be

when(productRepository.findAllByCategoryUuid(categoryUuid)
                .thenReturn(productList);   

here you are mocking productRepository.findAllByCategoryUuid as your goal is to test the method in service class.

after this just add appropriate assert statements for all the conditions mentioned above.

Also I usually follow a rule whenever bug is logged against some code I try to cover that input and output case using assert in my Junit so that every-time I will test all the possible input and output scenarios.

00Shunya
  • 85
  • 10
  • Thanks a lot for this good explanations. But, I am wondering, if we test service method, why do we retrieve data from repository method? –  Jun 27 '21 at 19:58
  • Any reply please? –  Jun 27 '21 at 20:40
  • 1
    we are mocking the call to repository as you service is calling repository method we need a way to avoid that call and return a dummy data. So as I stated earlier the purpose of unit test is just to test your service method now here if there is something wrong in your repo method it will be difficult to find that out and its also not the purpose here for that you need to write separate test case for repo layer. this is why are just mocking the call to repo and returning dummy data created earlier. Hope this helps I am in different timezone so there is some delay in answers. – 00Shunya Jun 28 '21 at 03:34
  • Thanks a lot for your valuable helps, voted up ;) On the other hand, could you pls post a proper Unit Test scenario according to related entities? I mean that, suppose there are 2 entities like Employee and City (there is City.Id == Employee.CityId relation) and we want to test the sorting. In this scene, (in test method), I need to mock or create Employee first and then mock or create City record. Then pass the employee record to the service for testing. How can I build a simple unit test based on this scenario? I have tried many different scenario, but I could not create a proper test :((( –  Jun 28 '21 at 05:56
1

The important things to remember while writing Junit Tests using Mockito

  1. All class level @Runwith()
  2. Test class should be with @InjectMocks
  3. All tests should be annotated with @Test
  4. Any external service should be Mocked with @Mock
  5. Any calls going to DB or other services should be mocked and values should be returned accordingly.
  6. You should have assertions to test your result.

I would write something like this :

@RunWith(MockitoJUnitRunner.class)
public class ProductServiceImplTest {

@InjectMocks
ProductServiceImpl productService;

@Mock
ProductRepository productRepository;

@Test
public void testFindAllByCategoryUuid() {

    UUID categoryUuid = UUID.randomUUID();

    final List<Product> productList = new ArrayList<>();
    for (int i = 0; i < size; i++) {
        // create product by setting "categoryUuid" and add to productList
    }
    
    when(productRepository.saveAll(ArgumentMatchers.any()).thenReturn(productList); 
// Or below might work for newer version of test cases when we get Null Pointer Exp using older version of Junit test cases

//doReturn(productList).when(productRepository).saveAll(any(List.class));
    
    List<ProductDTO> response = productService.findAllByCategoryUuid(categoryUuid);
    
    Assert.assertNotNull(response);
    Assert.assertEquals("Object Value", response.getXXX());
}
TriS
  • 3,668
  • 3
  • 11
  • 25
  • Perfect explanations, I am trying to apply now and will inform you asap. –  Jun 27 '21 at 20:21
  • It throws NullPointer exception as `ArgumentMatchers.any()` is null. What is `ArgumentMatchers.any()` and how can I fix that problem? –  Jun 27 '21 at 20:39
  • ArgumentMatchers information : https://stackoverflow.com/questions/44287635/argumentmatchers-any-versus-the-other-matchers-in-mockito-2 . – TriS Jul 06 '21 at 10:27
  • Sometime due to the Mockito version we face similar kind if issues. The blow line fixes the Null pointer. You can modify according to you test condition. doReturn(result).when(repository).saveAll(any(List.class)); – TriS Jul 06 '21 at 10:29
  • Updated the test case with your query. – TriS Jul 06 '21 at 13:19
1

Writing unit tests against Service layer comes with drawbacks:

  1. You violate encapsulation of the method-under-test. If it changes because you start to invoke different classes/methods - the test will break. Even though the method may be working correctly.
  2. Because you intend to use mocking, partly your tests will be simply checking that your mocks are set up to pass the tests. So basically you'll be testing test logic.

It's usually more productive to move the logic down e.g. to the Model. Those classes could be then unit-tested without mocks. Then you could write a higher-level test (including DB) that checks that everything works together.

Reading:

Stanislav Bashkyrtsev
  • 14,470
  • 7
  • 42
  • 45
  • Thank you very much Stanislav for this wonderful explanations, voted up. You are right (I have no experience but I see you have experience on Unit Testing). For now I have to write this Unit test for the service method, but except from this of course I will follow this approach you mentioned. For now, could you pls clarify me about the following issues? –  Jun 27 '21 at 21:51
  • **#1** Actually I need to save related data to 3 tables with PK-FK relation (I omitted that part for brevity in the question). In this case I think I need to get saved product id's via `List productUuidList = productRepository.saveAll(productList)`. However, `productUuidList ` size always 0. Why? Should I get these values using `whenthenReturn()`? I also try this like `when(productRepository.saveAll(productList)).thenReturn(demoList);` ? (I set demoList an empty product list), but demoList is still empty. –  Jun 27 '21 at 21:58
  • @justice, if you go without mocks, then yes - you _do_ need to save those products first. You can use e.g. H2 in-memory DB for these tests or a real one installed locally. If you decide to go with mocks (which I personally think is a mistake), then you'll need to mock `productRepository.findAllByCategoryUuid()` - since this is the method that the Service invokes. Nothing will actually be stored in DB, so there's no need to mock `productRepository.saveAll(productList)` (unless the Service invokes it for some reason). – Stanislav Bashkyrtsev Jun 28 '21 at 03:35
  • Thanks for reply. But I would prefer most proper and easy way of course. On the other hand, I am wondering how to manage related entity records? Even if I use mock, I need to set the related PK & FK values of the related entities. I imagine something juts setting PK & FK values of the related entities without mocking the other fields. But as I am really too confused, could you pls post a proper answer according to my example? You could just give an example having related entities (setting their corresponding fields and test for example for sorting). Thanks in advance. –  Jun 28 '21 at 05:50
  • Any best practice for the scenario above? –  Jun 28 '21 at 18:38
  • @justice, most proper way - don't use mocks. Move complicated domain-related logic to the Domain Model. And things like Services should be kept pretty simple (after all it's a Service _Facade_). Then you'll need to write simple high-level tests with database and all. Read the articles that I linked in my answer, they go into more details. – Stanislav Bashkyrtsev Jun 28 '21 at 19:37
  • This is the best advice. – Kid Diamond Jul 06 '23 at 21:08