【问题标题】:Koin test declareMock not working with multiples declarationKoin 测试 declareMock 不适用于倍数声明
【发布时间】:2020-08-14 13:45:47
【问题描述】:

我正在尝试声明两个模拟,但我得到一个 java.lang.ClassCastException。

我正在尝试测试我的 MainActivity(它有一个 MainViewModel),所以我需要模拟 MainViewModel。 问题是当我启动 MainActivity 进行测试时,SplashFragment 被注入,因为我正在使用导航组件并且我的 MainActivity 包含一个 NavHostFragment 并且默认情况下启动 SplashFragment(它有一个 SplashViewModel)。

总结:App -> MainActivity(尝试测试MainActivity中的一些常用组件) -> Splash Fragment

private val testViewState = MediatorLiveData<MainViewState>()
private val testSplashViewState = MediatorLiveData<SplashViewState>()

@Rule
@JvmField
var mockSplashViewModelRule = MockProviderRule.create {
    mock(SplashViewModel::class.java)
}

@Rule
@JvmField
var mockViewModelRule = MockProviderRule.create {
    mock(MainViewModel::class.java)
}


private fun launchMainActivity() {
    declareMock<SplashViewModel> {
        given(viewState).willReturn(testSplashViewState)
    }

    declareMock<MainViewModel> {
        given(viewState).willReturn(testViewState)
    }
    activityScenarioRule.scenario
}

我收到此错误: java.lang.ClassCastException:SplashViewModel 无法转换为 MainViewModel

如果我没有声明我得到的 SplashViewModel: java.lang.RuntimeException:org.koin.core.error.NoBeanDefFoundException:找不到类的定义:'SplashViewModel'。检查你的定义!

有什么想法吗?

【问题讨论】:

    标签: android unit-testing kotlin mocking koin


    【解决方案1】:

    我是这样解决的:

    private val testSplashViewState = MediatorLiveData<SplashViewState>()
    private val testViewState = MediatorLiveData<MainViewState>()
    
    @Mock
    private lateinit var mainViewModel: MainViewModel
    
    @Mock
    private lateinit var splashViewModel: SplashViewModel
    
    @Rule
    @JvmField
    var activityScenarioRule = LateInitActivityScenarioRule(MainActivity::class.java, false)
    
    val module = module(createdAtStart = true, override = true) {
        viewModel { splashViewModel }
        viewModel { mainViewModel }
    }
    
    @Before
    fun setupMainActivityTest() {
        loadKoinModules(module)
    }
    
    @After
    fun afterMainActivityTest() {
        unloadKoinModules(module)
    }
    
    private fun launchMainActivity() {
        testSplashViewState.value = SplashViewState()
        whenever(splashViewModel.viewState).thenReturn(testSplashViewState)
    
        testViewState.value = MainViewState()
        whenever(mainViewModel.viewState).thenReturn(testViewState)
        activityScenarioRule.scenario
    }
    
    
    
    
    
    /**
     * Copyright 2018 The Android Open Source Project
     * <p>
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     * <p>
     * http://www.apache.org/licenses/LICENSE-2.0
     * <p>
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    import android.app.Activity;
    import android.content.Intent;
    
    import androidx.annotation.Nullable;
    import androidx.test.core.app.ActivityScenario;
    
    import org.junit.rules.ExternalResource;
    
    import static androidx.test.internal.util.Checks.checkNotNull;
    
    /**
     * PrincipalActivityScenarioRule launches a given activity before the test starts and closes after the test.
     *
     * <p>You can access to scenario interface via {@link #getScenario()} method. You may finish your
     * activity manually in your test, it will not cause any problems and this rule does nothing after
     * the test in such cases.
     *
     * <p>This rule is an upgraded version of {@link androidx.test.rule.ActivityTestRule}. The previous
     * version will be deprecated and eventually be removed from the library in the future.
     *
     * <pre>{@code
     * Example:
     *  }{@literal @Rule}{@code
     *   PrincipalActivityScenarioRule<MyActivity> rule = new PrincipalActivityScenarioRule<>(MyActivity.class);
     *
     *  }{@literal @Test}{@code
     *   public void myTest() {
     *     ActivityScenario<MyActivity> scenario = rule.getScenario();
     *     // Your test code goes here.
     *   }
     * }</pre>
     */
    public final class LateInitActivityScenarioRule<A extends Activity> extends ExternalResource {
    
        /**
         * Same as {@see java.util.function.Supplier} which requires API level 24.
         *
         * @hide
         */
        interface Supplier<T> {
            T get();
        }
    
        private Supplier<ActivityScenario<A>> scenarioSupplier;
        @Nullable
        private ActivityScenario<A> scenario;
    
        private boolean initOnBefore;
    
        /**
         * Constructs PrincipalActivityScenarioRule for a given activity class.
         *
         * @param activityClass an activity class to launch
         */
        public LateInitActivityScenarioRule(Class<A> activityClass, boolean initOnBefore) {
            this.initOnBefore = initOnBefore;
            scenarioSupplier = () -> ActivityScenario.launch(checkNotNull(activityClass));
        }
    
        /**
         * Constructs PrincipalActivityScenarioRule for a given activity class and intent.
         *
         * @param startActivityIntent an intent to start the activity
         */
        public LateInitActivityScenarioRule(Intent startActivityIntent, boolean initOnBefore) {
            this.initOnBefore = initOnBefore;
            scenarioSupplier = () -> ActivityScenario.launch(checkNotNull(startActivityIntent));
        }
    
        @Override
        protected void before() throws Throwable {
            if (initOnBefore) {
                ensureScenario();
            }
        }
    
        @Override
        protected void after() {
            if (scenario != null) {
                scenario.close();
            }
        }
    
        /**
         * Returns {@link ActivityScenario} of the given activity class.
         *
         * @return a non-null {@link ActivityScenario} instance
         * @throws {@see NullPointerException} if you call this method while test is not running
         */
        public ActivityScenario<A> getScenario() {
            ensureScenario();
            return checkNotNull(scenario);
        }
    
        /**
         * Returns {@link ActivityScenario} of the given activity class.
         *
         * @return a non-null {@link ActivityScenario} instance
         * @throws {@see NullPointerException} if you call this method while test is not running
         */
        public ActivityScenario<A> createScenario(Intent intent) {
            scenarioSupplier = () -> ActivityScenario.launch(checkNotNull(intent));
            ensureScenario();
            return checkNotNull(scenario);
        }
    
        private void ensureScenario() {
            if (scenario == null) {
                scenario = scenarioSupplier.get();
            }
        }
    }
    

    【讨论】:

    • 你能提供更多关于什么是 LateInitActivityScenarioRule 的信息吗?
    • @joaoasilva 抱歉回复晚了,我已经编辑了帖子并添加了关于 LateInitActivityScenarioRule 的代码,一切顺利
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多