Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Settings in Unit Testing #194

Open
kostapostolakis opened this issue May 18, 2024 · 4 comments
Open

Use Settings in Unit Testing #194

kostapostolakis opened this issue May 18, 2024 · 4 comments

Comments

@kostapostolakis
Copy link

kostapostolakis commented May 18, 2024

Hello, I have created this helper to save userName in Settings. I get and set the details in some viewModels.

class SettingsHelper {
    companion object {
        // User Data
        const val USER_NAME = "userName"
    }

    var userName: String?
        get() { return Settings().getStringOrNull(USER_NAME) }
        set(value) { Settings()[USER_NAME] = value }
}

I created the following unit test:

class EditProfileTest {
    @Test
    fun test_dataHaveChanged() {
        SettingsHelper().userName = "My Name"

        val editProfileViewModel = EditProfileViewModel()

        // When no changes, method returns false
        assertFalse(editProfileViewModel.dataHaveChanged())

        // When the userName is empty, method returns false
        editProfileViewModel.updateUserName("")
        assertFalse(editProfileViewModel.dataHaveChanged())
    }
}

And I get this error:

java.lang.NullPointerException
	at com.russhwolf.settings.NoArgKt.Settings(NoArg.kt:32)
	at gr.palermonights.mobileapp.helpers.SettingsHelper.setUserName(SettingsHelper.kt:48)
	at gr.palermonights.mobileapp.EditProfileTest.test_dataHaveChanged(EditProfileTest.kt:12)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.base/java.lang.reflect.Method.invoke(Unknown Source)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:112)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:40)
	at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:60)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:52)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.base/java.lang.reflect.Method.invoke(Unknown Source)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at jdk.proxy1/jdk.proxy1.$Proxy2.processTestClass(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:176)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
	at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

How can I use the helper in Unit Testing?

@russhwolf
Copy link
Owner

Calling the Settings() factory function invokes SharedPreferences APIs on Android, which aren't available in unit tests. Starting in 1.2 it will be possible to call SettingsInitializer().create(context) with a robolectric or other mock Context object for unit testing. Otherwise, you can inject a Settings object rather than hardcoding the Settings() call, and pass MapSettings() in tests.

@SirFilbido
Copy link

Calling the factory function invokes APIs on Android, which aren't available in unit tests. Starting in 1.2 it will be possible to call with a robolectric or other mock object for unit testing. Otherwise, you can inject a object rather than hardcoding the call, and pass in tests.Settings()``SharedPreferences``SettingsInitializer().create(context)``Context``Settings``Settings()``MapSettings()

Hey guys,

I didn't quite understand the answer. In my case, I'm doing unit testing at CommonMainTest in KMP(old KMM). I tried using KoinTest and Mockative and both returned the same error presented initially.

@russhwolf could you explain better how to resolve this issue when testing?

@zivkovic
Copy link

Is there a timeframe for when 1.2 would be released?

@russhwolf
Copy link
Owner

1.2 is now out. You can now do something like this in a test

val context: Context = ...
SettingsInitializer().create(context)

val settings = Settings()
// do something with settings

Alternatively, make your helper class injectable:

class SettingsHelper(val settings: Settings = Settings()) {
    companion object {
        // User Data
        const val USER_NAME = "userName"
    }

    var userName: String?
        get() { return settings.getStringOrNull(USER_NAME) }
        set(value) { settings[USER_NAME] = value }
}

then you can do SettingsHelper(MapSettings()) in your tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants