Mock — این چیست، اشیاء mock و کتابخانه‌های تست

نویسنده: IT Sectr منتشر شده: 2026-04-10 زمان مطالعه: 8 دقیقه

Mock — یک شیء جایگزین است که رفتار کامپوننت واقعی را شبیه‌سازی می‌کند و امکان بررسی تعامل با آن را فراهم می‌کند. برخلاف Stub که صرفاً یک مقدار مشخص را برمی‌گرداند، Mock واقعیت فراخوانی متد، آرگومان‌های ارسال‌شده و تعداد فراخوانی‌ها را ثبت می‌کند. طبق داده‌های Mockito (2024)، Mock محبوب‌ترین نوع Test Double در پروژه‌های Java و Kotlin است که در بیش از 70% تست‌های واحد برنامه‌های موبایل استفاده می‌شود.

نکات اصلی

  • Mock — شیء بررسی‌کننده تعامل: کدام متدها فراخوانی شدند، با چه آرگومان‌هایی و چند بار
  • Mockito — محبوب‌ترین کتابخانه برای ایجاد Mock در پروژه‌های Java و Android
  • MockK — جایگزین Mockito برای Kotlin با پشتیبانی بومی از کوروتین‌ها و sealed class
  • Behavior verification — تفاوت کلیدی Mock و Stub: Mock رفتار را بررسی می‌کند، نه وضعیت را
  • Over-mocking — Anti-Pattern اصلی: mock باید فقط برای وابستگی‌های خارجی باشد

Mock چیست؟

Mock — شیءای است که توسط فریمورک mocking (Mockito, MockK, EasyMock) ایجاد می‌شود، از یک واسط یا کلاس تقلید می‌کند و تمام فراخوانی‌های متدهای خود را ثبت می‌کند. برنامه‌نویس انتظارات را تعیین می‌کند: متد X با آرگومان‌های Y فراخوانی می‌شود و Z را برمی‌گرداند. پس از اجرای تست، Mock بررسی می‌کند که آیا انتظارات با فراخوانی‌های واقعی مطابقت دارند یا خیر.

این اصطلاح از استعاره تئاتری Test Doubles گرفته شده است: Mock یک «تقلیدکننده» است که نه تنها روی صحنه می‌ایستد (مانند Dummy)، بلکه نقش بازی می‌کند و بررسی می‌کند که تعامل با آن به درستی انجام شده است. اگر کد تست‌شده متدی را که Mock انتظار داشت فراخوانی نکرد یا با آرگومان‌های نادرست فراخوانی کرد — تست با پیام نقض انتظار شکست می‌خورد.

Mock چگونه کار می‌کند

Mock از طریق کارخانه فریمورک ایجاد می‌شود: mockk<MyInterface>() یا Mockito.mock(MyClass.java). فریمورک یک شیء پروکسی تولید می‌کند که تمام فراخوانی‌های متد را رهگیری می‌کند. هر فراخوانی با انتظارات (expectations) از پیش تعیین‌شده مقایسه می‌شود. اگر فراخوانی با انتظار مطابقت داشت — مقدار مشخص‌شده برگردانده می‌شود. اگر نه — Mock بسته به پیکربندی، مقدار پیش‌فرض برمی‌گرداند یا استثنا پرتاب می‌کند.

چه زمانی Mock ضروری است

Mock زمانی اجباری است که کد تست‌شده با کامپوننت‌های دارای عوارض جانبی تعامل دارد: ارسال داده به سرور، نوشتن در پایگاه داده، لاگ‌نویسی، آنالیتیکس، ناوبری، نمایش دیالوگ‌های سیستمی. بدون Mock این تعاملات بدون راه‌اندازی زیرساخت واقعی قابل بررسی نیستند. به گفته Google Testing Blog، Mock تنها راه بررسی اینکه برنامه واقعاً رویداد آنالیتیکس را ارسال کرده است بدون بالا آوردن سرور تست است.

Mock و Stub: مقایسه دقیق

تفاوت بین Mock و Stub یکی از بحث‌برانگیزترین موضوعات در تست‌نویسی است. هر دو نوع وابستگی واقعی را جایگزین می‌کنند، اما با روش‌های اساساً متفاوت.

معیارMockStub
سوال اصلیآیا متد فراخوانی شد؟چه نتیجه‌ای برگردانده شد؟
بررسیرفتار (verify)وضعیت (assert)
بازگشت دادهاختیاریاجباری
مثالverify(analytics).logEvent("click")assertEquals(5, repository.getCount())
زمان استفادهعوارض جانبیبازگشت داده

قاعده عملی: Mock یا نه

تست ساده برای انتخاب: از خود بپرسید — «اگر این خط کد را حذف کنم، تست شکست می‌خورد؟». اگر تست مقدار برگشتی را بررسی می‌کند — Stub نیاز است (بررسی با assert). اگر تست بررسی می‌کند که کد متد را با آرگومان‌های صحیح فراخوانی کرده است — Mock نیاز است (بررسی با verify). این دوگانگی از الگوی Command-Query Separation ناشی می‌شود: متدهای تغییردهنده وضعیت (commands) نیاز به Mock دارند؛ متدهای برگرداننده داده (queries) نیاز به Stub دارند.

Mockito و MockK: مقایسه کتابخانه‌ها

انتخاب بین Mockito و MockK یکی از اولین تصمیمات در تنظیم پشته تست پروژه Android در Kotlin است. هر دو کتابخانه یک وظیفه را انجام می‌دهند، اما با رویکرد متفاوت به ویژگی‌های خاص Kotlin.

Mockito: کلاسیک اثبات‌شده

Mockito — استاندارد دوفاکتو برای پروژه‌های Java است. نسخه 5.x به لطف MockMaker داخلی از اشیاء mock برای کلاس‌های نهایی، متدهای استاتیک و سازنده‌ها پشتیبانی می‌کند. برای پروژه‌های Kotlin، Mockito نیاز به پیکربندی اضافی دارد: پسوندهای mockito-kotlin برای نحو بهبودیافته، mockito-inline برای کلاس‌های نهایی. Mockito بدون آداپتورهای اضافی از کوروتین‌های Kotlin و توابع suspend پشتیبانی نمی‌کند.

MockK: رویکرد Kotlin-first

MockK به طور خاص برای Kotlin ساخته شده است. این کتابخانه به طور بومی از کوروتین‌ها (coEvery, coVerify)، sealed class، data class، singleton‌های object و توابع extension پشتیبانی می‌کند. نحو MockK از DSL با بلوک‌های لامبدا استفاده می‌کند که در کد Kotlin طبیعی به نظر می‌رسد. MockK همچنین می‌تواند بدون پیکربندی اضافی ویژگی‌ها (property mocking) را mock کند — این برای پروژه‌های Android که از LiveData، StateFlow و Delegates استفاده می‌کنند مهم است.

kotlin
// Mockito + mockito-kotlin
val repository = mock<UserRepository>()
whenever(repository.getUser(1)).thenReturn(user)

// MockK
val repository = mockk<UserRepository>()
every { repository.getUser(1) } returns user

مقایسه عملکرد

معیارها (JVM Benchmark, 2024) نشان می‌دهد که MockK اشیاء mock را 15–20% سریع‌تر از Mockito برای پروژه‌های Kotlin ایجاد می‌کند، زیرا مستقیماً با بایت‌کد Kotlin کار می‌کند نه Java Reflections. برای پروژه‌هایی با هزاران تست واحد، تفاوت در زمان ساخت ممکن است قابل توجه باشد: MockK در اجرای کامل تست‌ها در پروژه‌های بزرگ 30–60 ثانیه صرفه‌جویی می‌کند.

نمونه‌های تست Mock در Kotlin

سه سناریو را بررسی می‌کنیم: تست ViewModel با وابستگی‌های Mock، تست UseCase با بررسی فراخوانی API و تست کوروتین‌ها با coVerify.

مثال 1: ViewModel با آنالیتیکس Mock شده

kotlin
class ProfileViewModelTest {
    private val analytics = mockk<AnalyticsService>()
    private val repo = mockk<UserRepository>()
    private val vm = ProfileViewModel(repo, analytics)

    fun `profile opened logs analytics event`() {
        every { analytics.logEvent("profile_opened") } returns Unit

        vm.onViewCreated()

        verify { analytics.logEvent("profile_opened") }
    }
}

مثال 2: UseCase با بررسی ناهمگام

kotlin
class SendMessageUseCaseTest {
    private val api = mockk<MessagingApi>()
    private val useCase = SendMessageUseCase(api)

    fun `send message with correct payload`() = runTest {
        val message = Message(text = "Hello", userId = 42)

        coEvery { api.sendMessage(any()) } returns MessageResult.Sent("msg_1")

        val result = useCase.execute(message)

        coVerify {
            api.sendMessage(match {
                it.text == "Hello" && it.userId == 42
            })
        }
        assertTrue(result is MessageResult.Sent)
    }
}

مثال 3: بررسی آرگومان‌ها با ArgumentCaptor

kotlin
class OrderUseCaseTest {
    private val api = mockk<OrderApi>()
    private val useCase = OrderUseCase(api)
    private val slot = slot<OrderRequest>()

    fun `order request contains correct items`() = runTest {
        coEvery { api.placeOrder(capture(slot)) } returns OrderResult.Placed("order_1")

        useCase.execute(listOf("item_a", "item_b"))

        assertEquals(2, slot.captured.items.size)
        assertEquals("item_a", slot.captured.items[0])
    }
}

بهترین روش‌های تست با Mock

استفاده مؤثر از Mock در توسعه موبایل نیازمند رعایت انضباط است. نقض این قوانین تست‌ها را به مانع شکننده‌ای تبدیل می‌کند که در هر بازسازی دچار شکست می‌شوند.

فقط مرزهای خارجی برنامه را mock کنید

قاعده سخت: Mock فقط برای وابستگی‌هایی ایجاد می‌شود که از مرز برنامه عبور می‌کنند: مشتریان API، پایگاه‌های داده، سیستم فایل، سرویس‌های سیستمی (LocationManager, BluetoothAdapter, Camera). کلاس‌های داخلی برنامه — موجودیت‌های دامنه، Value Object، ابزارهای ساده — نباید با Mock جایگزین شوند. رفتار آنها از طریق اشیاء واقعی تست می‌شود.

یک assert/verify در هر تست

هر تست باید دقیقاً یک بررسی منطقی داشته باشد — یا verify (برای Mock)، یا assert (برای Stub). بررسی وضعیت و رفتار را در یک تست مخلوط نکنید. اگر هم فراخوانی API و هم نتیجه باید بررسی شوند — دو تست جداگانه با نام‌های متفاوت ایجاد کنید. این قاعده که به «یک assert در هر تست» معروف است، به توصیه‌های Kent Beck (2002) برمی‌گردد.

  • از relaxUnitFun = true در MockK استفاده کنید برای متدهایی که Unit برمی‌گردانند — در غیر این صورت Mock برای فراخوانی توصیف‌نشده استثنا پرتاب می‌کند
  • verify را فقط به فراخوانی‌های حیاتی محدود کنید — هر getter و setter را بررسی نکنید، این کار تست‌ها را شکننده می‌کند
  • از ArgumentMatchers به‌طور معنی‌دار استفاده کنید — any() جزئیات مهم را پنهان می‌کند اگر آرگومان برای منطق کسب‌وکار حیاتی است
  • از verifyNoMoreInteractions سوءاستفاده نکنید — این متد تست را نسبت به هر تغییری در کد تولیدی به‌طور غیرضروری سخت‌گیر می‌کند
  • از @MockkAnnotations استفاده کنید برای مقداردهی خودکار اشیاء Mock — این کار کدهای تکراری را کاهش و خوانایی را بهبود می‌بخشد

تکنیک‌های پیشرفته تست با Mock

علاوه بر mocking پایه، تکنیک‌های پیشرفته‌ای وجود دارند که مسائل خاص در توسعه موبایل را حل می‌کنند: تست هم‌روندی، بررسی وضعیت Flow و mocking جزئی اشیاء واقعی.

Partial Mock با spyK

Spy (یا partial mock) امکان ایجاد شیءای را فراهم می‌کند که فراخوانی‌ها را به پیاده‌سازی واقعی واگذار می‌کند، اما اجازه بازنویسی متدهای خاص را می‌دهد. در MockK، spyk بر اساس نمونه واقعی کلاس ایجاد می‌شود: val repo = spyk(InMemoryUserRepository()). فراخوانی‌هایی که انتظارات برای آنها از طریق every تعیین شده است از Mock عبور می‌کنند؛ بقیه — از شیء واقعی. Spy به‌ویژه برای تست کدهای قدیمی که تزریق وابستگی هنوز در آنها پیاده‌سازی نشده مفید است، زمانی که فقط باید یک متد را بازنویسی کرد.

تست StateFlow با Turbine

در پروژه‌های مدرن Android با Jetpack Compose، ViewModel وضعیت را از طریق StateFlow آشکار می‌کند. MockK امکان mock کردن وابستگی‌های Flow را فراهم می‌کند و کتابخانه Turbine بررسی انتشار را ساده می‌کند. الگوی کلاسیک: MockK برای UseCase که Flow برمی‌گرداند، Turbine برای بررسی انتشار ViewModel. این پشته توسط مستندات Android Testing (Google, 2024) برای پروژه‌های مبتنی بر Kotlin Coroutines توصیه می‌شود.

kotlin
class SearchViewModelTest {
    private val searchUseCase = mockk<SearchUseCase>()
    private val vm = SearchViewModel(searchUseCase)

    fun `search emits results`() = runTest {
        coEvery { searchUseCase.search("android") } returns
            flowOf(SearchResult.Success(listOf(Item("Android TDD"))))

        vm.search("android")

        vm.state.test {
            val state = awaitItem()
            assertTrue(state.items.isNotEmpty())
            cancelAndIgnoreRemainingEvents()
        }
    }
}

سوالات متداول

تفاوت Mock با Mockito چیست؟

Mock — یک مفهوم، نوع Test Double بررسی‌کننده رفتار است. Mockito — کتابخانه‌ای برای ایجاد اشیاء Mock در Java و Android است. کتابخانه‌های دیگر: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS).

Mock چگونه با کوروتین‌های Kotlin کار می‌کند؟

برای تست توابع suspend با Mock از MockK (coEvery / coVerify) یا Mockito با mockito-kotlin استفاده کنید. MockK از کوروتین‌ها به‌طور بومی پشتیبانی می‌کند: coEvery رفتار تابع suspend را تعیین می‌کند، coVerify فراخوانی آن را درون کوروتین بررسی می‌کند. تمام فراخوانی‌های suspend باید درون runTest (kotlinx-coroutines-test) اجرا شوند.

آیا Mock می‌تواند در فراخوانی‌های مکرر مقادیر متفاوت برگرداند؟

بله. در MockK برای این کار از returnsMany استفاده می‌شود: every { api.getData() } returnsMany listOf(response1, response2). در Mockito — زنجیره thenReturn(value1).thenReturn(value2). این برای تست رفتار در فراخوانی‌های متوالی با پاسخ‌های متفاوت مفید است.

چگونه وضعیت Mock را بین تست‌ها پاک کنیم؟

در MockK از annotation @MockK با فیلد relaxed = true استفاده کنید و در متد @After clearMocks(mock) را فراخوانی کنید. در MockitoMockito.reset(mock). بهترین روش: برای هر تست یک Mock جدید از طریق @Before ایجاد کنید تا تأثیر بین تست‌ها حذف شود.

Mock چگونه sealed class را در Kotlin مدیریت می‌کند؟

MockK به درستی با sealed class کار می‌کند: every { useCase() } returns Result.Success(data). Mockito مستقیماً از sealed class پشتیبانی نمی‌کند و نیاز به روش‌های جایگزین دارد. این یکی از دلایلی است که برای پروژه‌های Kotlin به جای Mockito، MockK توصیه می‌شود.

خلاصه

  • Mock — نوع Test Double که رفتار (verify) را بررسی می‌کند نه وضعیت (assert) وابستگی‌ها
  • Mockito — استاندارد برای Java/Android، MockK — انتخاب Kotlin-first با پشتیبانی کوروتین و sealed class
  • قاعده اصلی: Mock برای مرزهای خارجی (شبکه، DB، سرویس‌های سیستمی)، اشیاء واقعی برای کلاس‌های داخلی
  • Over-mocking — Anti-Pattern اصلی: جایگزینی بیش از حد وابستگی‌ها تست‌ها را شکننده و کم‌فایده می‌کند
  • یک تست — یک بررسی منطقی: verify برای Mock یا assert برای Stub، نه هر دو در یک تست
  • ArgumentCaptor / slot — روش صحیح بررسی آرگومان‌های فراخوانی Mock به جای any() کور
  • MockK برای پروژه‌های Kotlin توصیه می‌شود: coEvery و coVerify بدون آداپتورهای اضافی به‌طور بومی با کوروتین‌ها کار می‌کنند

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید