مقدمه ای بر تست نویسی در جاوا، mockito قسمت ۲

ماکیتو یک فریمورک ماک هست که mock در معنی لغوی میتوان به " تقلید کردن " رسید. در اصطلاح فنی وقتی ما میخواهیم متدی را تست کنیم که‌ این متد ممکنه به آبجکت های خارجی وابسته باشه که این آبجکت ها در هنگام اجرای اپلیکیشن فراهم میشن، مثلا یک متدی که با دیتابیس ارتباط میگیره. کار ماک کردن به اینصورت هست که یک آبجکت در اصطلاح dummy یا بی خاصیت ایجاد میکنه که ما بتونیم این وابستگی رو برای متد در حالت ایزوله فراهم کنیم که بتوانیم برای این متد تست واحد بنویسیم. برای مثال این قسمت، اول یک پروژه spring ایجاد میکنیم و معماری MVC رو پیاده میکنیم و سعی میکنیم لایه service این اپ رو تست کنیم. لایه سرویس ما چون وابسته به لایه repository هست و برای تست لایه سرویس لازم هست که context اسپرینگ لود بشه و آبجکت های لایه ریپازیتوری رو برامون فراهم کنه. اما ماکیتو به کمک ما میاد که دیگه نیایم کانتکس رو لود کنیم تا آبجکت ها برامون خودکار ساخته بشن، بلکه ما خودمون بوسیله این فریمورک میایم و آبجکت هامونو خودمون بصورت ایزوله و بدون لود کردن کانتکس اسپرینگ ایجاد میکنیم و استفاده میکنیم. مزیت اینکار سرعت بالای اجرای تست ها در مقایسه با تست یکپارچه هستپروژه ای که ایجاد میکنیم،‌به ۳ دپندنسی نیاز داریم:1- Spring web2- Lombok3- H2 database4- Spring data jpagradle: implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test'تنظیمات دیتابیس رو توی فایل application.properties یا application.yml انجام میدیم:(به فاصله ها دقت کنید)نکته: تنظیمات دیتابیس فقط برای این هست که اپلیکیشن در حالت عادی اجرا بشه.spring: jpa: show-sql: true hibernate: ddl-auto: create datasource: driver-class-name: org.h2.Driver url: jdbc:h2:mem:db username: sa password: saلایه مدل:import lombok.*; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import java.math.BigDecimal; @Entity @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @Builder public class Product { @Id @GeneratedValue private Long id; private String name; private String description; private BigDecimal price; }لایه ریپازیتوری:import ir.darkdeveloper.mocking.model.Product; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface ProductRepo extends JpaRepository<Product, Long> { }و در آخر لایه سرویس رو که قراره تست کنیم:import ir.darkdeveloper.mocking.model.Product; import ir.darkdeveloper.mocking.repo.ProductRepo; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class ProductService { private final ProductRepo repo; public List<Product> getAll() { // your logic goes here // and this logic is going to be tested return repo.findAll(); } public Product getById(Long Id){ // your logic goes here // and this logic is going to be tested return repo.findById(Id).orElse(null); } }برای تست این لایه یک کلاس تست در مسیری test ایجاد میکنیم:import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.when; class ProductServiceTest { @Test void getAll() { } @Test void getById() { } }هدف این هست که ما لایه سرویس رو در این تست ایزوله کنیم یعنی تمرکز اصلی تست رو روی این لایه بذاریم برای اینکار نیازه که وابستگی های این سرویس رو mock کنیم. در حال حاضر ما توی سرویس مون فقط یک وابستگی اونم به ریپازیتوری داریم که نیازه که ماک بشه. برای این منظور لازمه که بالای کلاس تست، این انوتیشین رو بنویسیم:@ExtendWith(MockitoExtension.class)و ریپازیتوری رو به این صورت ازش آبجکت ماک میسازیم:@Mock private ProductRepo repo;در نهایت همچین کدی خواهیم داشت:@ExtendWith(MockitoExtension.class) class ProductServiceTest { @Mock private ProductRepo repo; private ProductService service; @BeforeEach void setUp() { service = new ProductService(repo); } @Test void getAll() { } @Test void getById() { } }ما اومدیم یک آبجکت هم از productService ساخته ایم و توی متد setUp که با انوتیشن BeforeEach مشخص شده است، هربار قبل از اجرای متد های تست،‌ productSerive رو با ریپازیتوری ماک شده، مقداردهی میکنیم.برای تست کردن متد findAll همچین کدی رو پیاده کرده ایم:@Test void getAll() { // given var given = List.of( new Product(1L, &quotname&quot, &quotdescription&quot, new BigDecimal(1)), new Product(2L, &quotname2&quot, &quotdescription2&quot, new BigDecimal(1)), new Product(2L, &quotname3&quot, &quotdescription3&quot, new BigDecimal(&quot136.54&quot)) ); // when when(repo.findAll()).thenReturn(given); // then var products = service.getAll(); assertThat(products.size()).isEqualTo(3); }در بخشی که با کامنت given نشون داده ایم دیتایی که قراره repository برگردونه رو مشخص کردیم.در بخش when این موضوع رو مشخص کرده ایم که وقتی متد findAll از ریپازیتوری فراخونی شد، چه چیزی رو برگردونه. در این بخش هست که ما لایه سرویس رو ایزوله کردیم و نیاز نیست که دیتابیس رو ران کنیم و مقادیر رو وارد کنیم و ...و در بخش then میایم و متدی که قراره توی لایه سرویس تست بشه رو فراخوانی میکنیم و بررسی های لازم رو روی مقدار بازگشتی از این متد رو بررسی میکنیم.در واقع در این پروسه ما اومدیم فقط تمرکزمون رو روی تست کردن بخش لاجیک لایه سرویس قرار دادیم و کاری با درست کار کردن لایه ریپازیتوری نداریم (تمام متد های پیشفرض لایه ریپازیتوری همگی ایمن هستند چون این متد هارو تیم spring فراهم کردن و همشون تست شده اند، اما برای کوئری های کاستوم لازمه که تست هایی نوشته بشود که بعدا در بخش دیگه ای بررسی خواهیم کرد)برای تست دوم هم همچین کدی داریم:@Test void getById() { // given var given = new Product(1L, &quotname&quot, &quotdescription&quot, new BigDecimal(1)); // when when(repo.findById(1L)).thenReturn(Optional.of(given)); // then var product = service.getById(1L); assertThat(product.getId()).isEqualTo(1L); assertThat(product.getName()).isEqualTo(&quotname&quot); assertThat(product.getDescription()).isEqualTo(&quotdescription&quot); }که بازم در اینجا بخش given رو داریم که دیتا رو آماده میکنیمدر بخش when این دیتا رو به این متد ریپازیتوری دادیمو در بخش then متدی که قراره تست بشه رو فراخونی میکنیم و مقدار بازگشتی اون رو بررسی میکنیمکاری که ما توی بخش when میکنیم اصطلاحا بهش میگن stubbingلینک این پروژه: LINKاین بخش به پایان رسید گرچه خیلی مسائل باقی است که خودتون میتونید جاواداک های عالی ماکیتو رو بخونید و ازش در تست هاتون استفاده. اگر نظری، پیشنهادی،‌ اصلاحی دارید، لطفا در کامنت ها من و بقیه رو مطلع کنید ?