The class under test looks like:
class State(pivate val repo){
val values = listOf<Int>()
fun update() {
values = repo.generateValues() // <-line 375
}
}
The unit test looks like:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
private class StateTest {
#MockK(relaxed = true) private lateinit var mockedRepo: Repo
#BeforeAll
fun config() {
MockKAnnotations.init(this)
}
#BeforeEach
fun setup() {
clearAllMocks()
unmockkAll()
}
#Test
fun `invoke update`() {
val state = mockk<State>(relaxed = true)
every { state.repo } answers { mockedRepo }
every { mockedRepo.generateValues() } returns listOf(1,2,3)
every { state.update() } answers { callOriginal() }
state.update()
Assertions.assertTrue(state.values.size > 0)
}
}
Runnig the test, a NullPointerException is thrown:
java.lang.NullPointerException
at com.name.someapp.someservice.State.update(State.kt:375)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at io.mockk.proxy.jvm.advice.MethodCall.call(MethodCall.kt:14)
at io.mockk.proxy.jvm.advice.SelfCallEliminatorCallable.call(SelfCallEliminatorCallable.kt:14)
at io.mockk.impl.instantiation.JvmMockFactoryHelper.handleOriginalCall(JvmMockFactoryHelper.kt:95)
at io.mockk.impl.instantiation.JvmMockFactoryHelper.access$handleOriginalCall(JvmMockFactoryHelper.kt:18)
at io.mockk.impl.instantiation.JvmMockFactoryHelper$mockHandler$1$invocation$$inlined$stdFunctions$lambda$1.invoke(JvmMockFactoryHelper.kt:27)
at io.mockk.impl.stub.MockKStub$handleInvocation$originalPlusToString$1.invoke(MockKStub.kt:230)
at io.mockk.MockKAnswerScope.callOriginal(API.kt:2205)
The reason you're getting the NullPointerException is that fun update() does not call getRepo() (which you've mocked) but instead it uses the backing field repo directly. (You can see this by compiling the Kotlin source to bytecode and decompiling to Java in IntelliJ IDEA.) This is also documented at kotlinlang.org:
ⓘ On the JVM: Access to private properties with default getters and setters is optimized to avoid function call overhead.
The answer is, as sidgate said in the comment, to create a real (not mock) instance of State and pass the mock repo to it constructor:
val state = State(mockRepo)
It is a code smell to mock the "system under test".
Related
I want to inquire how to mock a repository class and make a unit test for its functions.
Repository Class
class RequestRepository {
/** suspend function to get the result of token request from API*/
suspend fun getToken(userLoginModel: UserLoginModel): Response<TokenResponse> {
return ApiService.APILogin.getToken(userLoginModel)
}
// many more functions to come here
…
}
I tried this test function but it did not work
class LoginUnitTest {
private var repositoryTest = RequestRepository()
private var userLoginModelTest = Mockito.mock(UserLoginModel::class.java)
#Test
suspend fun login_with_correct_login_and_password() {
userLoginModelTest.email = "wrong mail"
userLoginModelTest.password = "wrong password"
var resultTest: Boolean = repositoryTest.getToken(userLoginModelTest).isSuccessful
if (resultTest)
assert(resultTest) { " we have issue in this function " }
else
assert(resultTest) { " we are good " }
}
}
I recieved this error message:
org.junit.runners.model.InvalidTestClassError: Invalid test class 'fr.mastergime.moez.arkindex.LoginUnitTest':
1. Method login_with_correct_login_and_password() should be void
2. Method login_with_correct_login_and_password should have no parameters
at org.junit.runners.ParentRunner.validate(ParentRunner.java:525)
at org.junit.runners.ParentRunner.<init>(ParentRunner.java:102)
at org.junit.runners.BlockJUnit4ClassRunner.<init>(BlockJUnit4ClassRunner.java:84)
at org.junit.runners.JUnit4.<init>(JUnit4.java:23)
at org.junit.internal.builders.JUnit4Builder.runnerForClass(JUnit4Builder.java:10)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:37)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
at org.junit.internal.requests.ClassRequest.createRunner(ClassRequest.java:28)
at org.junit.internal.requests.MemoizingRequest.getRunner(MemoizingRequest.java:19)
at org.junit.internal.requests.FilterRequest.getRunner(FilterRequest.java:36)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:49)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.CommandLineWrapper.main(CommandLineWrapper.java:64)
I notice four things here:
You ask for mocking of the repository, but instead mock the model class that you do not show.
You use JUnit 4, which is, well, ancient. Please use JUnit 5 Jupiter. (Or some Kotlin-specific framework.)
You use Mockito, which is not bad in general. For Kotlin, however, you should consider MockK, which can mock some of the things that are particular to Kotlin (like suspend-functions).
Finally, the real reason why you setup fails: You cannot have a suspend-function as a test method in JUnit. Remove the suspend keyword for login_with_correct_login_and_password.
even when I am using the spy method, I am not able to mock the getContext() method of attributesStorage() to get my context.
this is my code :
class Rich
{
fun method1() : HashMap<String,String>
{
val x = attributeStorage().getStore()
return x
}
}
class AttributeStorage
{
private fun getContext()
{
return MyProject.instance.context()
}
fun getStore()
{
//some work done,
return HashMap<String,String>()
}
}
#PrepareForTest(Rich::class)
class RichTest {
#Mock
lateinit var mcontext: Context
fun init()
{
mcontext = Mockito.mock(Context::class.java)
val mAttributesStorage = spy(AttributesStorage())
`when`<Context>(mAttributesStorage,"getContext").thenReturn(mcontext)
Mockito.`when`(mAttributesStorage.getStore()).thenReturn(mapOf("1" to "1"))
}
fun test()
{
//gives an error because the getContext() couldn't be mocked
}
}
I looked at every question possible on stack overflow and went through powermock and mockito documentation but couldn't find a solution to this.
#Mock
lateinit var mcontext: Context
and
mcontext = Mockito.mock(Context::class.java)
are one too many. Use either the one or the other (annotation preferred, of course).
See Shorthand for mocks creation - #Mock annotation:
Important! This needs to be somewhere in the base class or a test runner:
MockitoAnnotations.initMocks(testClass);
Regarding your last code comment: objects are mocked, methods are stubbed.
I have a kotlin Ut like below
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class FileOpenerTest {
private val file = mockk<Resource>()
private lateinit var target: FileOpener
#BeforeAll
fun setup() {
val file = File("./src/test/resources/sample.csv")
every { file.file } returns file
target = FileOpener(file)
}
#Test
fun `get documents for indexing from file`() {
val docs = target.startIndexing()
verify { docs.size == 3 }
}
}
the test case is always failing saying
kotlin.UninitializedPropertyAccessException: lateinit property target has not been initialized
But I am initialising it in the setup method, please help me to fix the issue ?
Your setup annotation #BeforeAll is applied only on static functions:
#BeforeAll annotated method MUST be a static method otherwise it will throw runtime error.
Source
So your method is not executed in JUnit. Either put the method and the field in your companion object or initialize it differently, like with #Before
My classes is written in Kotlin and here is my SharedPreferenceHandler
class SharedPreferenceHandler(sharedPrefs: SharedPreferences) {
companion object {
var mInstance: SharedPreferenceHandler = SharedPreferenceHandler(getPrefs())
private fun getPrefs(): SharedPreferences {
return Application.mInstance.getSharedPreferences(
"myApp", Context.MODE_PRIVATE)
}
fun getInstance(): SharedPreferenceHandler {
return mInstance
}
}
private var sharedPreferences = sharedPrefs
var accessToken: String?
get() = sharedPreferences.getString(SharedPreference.ACCESS_TOKEN.name, null)
set(token) = sharedPreferences.edit().putString(SharedPreference.ACCESS_TOKEN.name, token).apply()
}
Here is method called in presenter:
override fun reload(vm: ViewModel) {
super.updateViewModel(vm) {
//some stuffs
}
}
Here is my test method:
#Test
public void reload() {
when(SharedPreferenceHandler.Companion.getMInstance().getAccessToken()).thenReturn("234234234234234");
presenter.reload(viewModel);
}
In handler from super.updateViewModel(vm) I call "SharedPreferenceHandler.mInstance.accessToken!!)"
That is what is thrown:
Caused by: java.lang.IllegalStateException:
Application.mInstanc…m", Context.MODE_PRIVATE) must not be null
at
com.zuum.zuumapp.preferences.SharedPreferenceHandler$Companion.getPrefs(SharedPreferenceHandler.kt:18)
at
com.zuum.zuumapp.preferences.SharedPreferenceHandler$Companion.access$getPrefs(SharedPreferenceHandler.kt:14)
at
com.zuum.zuumapp.preferences.SharedPreferenceHandler.(SharedPreferenceHandler.kt:15)
I wanna to get accessToken by calling " SharedPreferenceHandler.mInstance.accessToken!!" in my test class.
Is possible to get that in my test method?
You can't use Android SharedPreferences in unit test, but you can mock your method call by this:
Mockito.`when`(SharedPreferenceHandler.mInstance.accessToken).thenReturn("token")
And return what you need.
You should not test your code this way. You should create an interface for class you want to mock:
interface MySharedPreferences {
fun getAccessToken(): String
}
Let your SharedPreferencesHandler implements this interface. Then in your presenter (or other class you want to test) inject dependencies (f.e. by constructor or framework like Dagger/Kodein) into your object. Then there is possibility to easy mock this interface. I assume in #Before you create class you test - and then just pass as param your mocked SharedPreferencesHandler.
Testing things with static dependencies is possible, but is but tricky (and a lot of people consider static dependencies as anti-pattern). How to do it is described here: How to android unit test and mock a static method
Example:
class MyPresenter(val sp: MySharedPreferences) {
/* some code here */
fun validateToken() {
if (sp.getAccessToken() == "") throw new Exception()
}
}
Like you see sp is injected into this class as parameter. Normally you don't create views/presenters etc. directly in code but by DI framework (like Dagger or Kodein). Anyway, static dependencies are not easy testable. Injected interface-dependencies can be mocked, and you operating not on object, but on behaviors (so it's bigger level of abstraction). So, now in your test all you have to do is:
class MyTest() {
#Mock lateinit var sharedPreferencesMock: MySharedPreferences
lateinit var instance: MyPresenter
#Before
fun setUp() {
instance = MyPresenter(sharedPreferencesMock)
}
#Test
fun testSomething() {
`when`(sharedPreferencesMock.getAccessToken()).thenReturn("myAccessToken")
/* here is your test body */
}
}
I am trying to set up my unit tests so that I can test my HBase Client. However, I am having trouble mocking the creation of the connection to HBase in the constructor. I don't think I am correctly injecting the mocked connection into the class I want to test, but I'm not sure where I made the mistake. I've looked at similar questions about mocking a connection, but all of those have the creation of the connection outside of the constructor.
This is the code I am trying to test:
#Lazy
#Service("HBaseClient")
public class HBaseClient {
/**
* Instantiate a new client and create connection to HBase.
*/
public HBaseClient() {
// Create connection to HBase
conf = HBaseConfiguration.create();
conf.setInt("timeout", 120000);
conf.set("hbase.zookeeper.quorum", zookeeperHost);
conf.set("hbase.zookeeper.property.clientPort", zookeeperPort);
conf.set("zookeeper.znode.parent", znodeParent);
try {
connection = ConnectionFactory.createConnection(conf);
} catch (IOException e) {
logger.error("Error creating connection to HBase - IOException");
}
}
public void addRecord(String rowKey, String columnFamily, Map<String, String> values) {
...
Here is what I have in the unit test:
#RunWith(PowerMockRunner.class)
public class TestHBaseClient {
#InjectMocks
private static HBaseClient hbaseClient;
#BeforeClass
#PrepareForTest({ConnectionFactory.class})
public static void setUpBeforeClass() throws Exception {
Connection mockConnection = PowerMockito.mock(Connection.class);
PowerMockito.mockStatic(ConnectionFactory.class);
PowerMockito.when(ConnectionFactory.createConnection()).thenReturn(mockConnection);
hbaseClient = new HBaseClient();
}
#AfterClass
public static void tearDownAfterClass() throws Exception {
}
#Test
public void testAddRecord() {
HashMap<String, String> values = new HashMap<String, String>();
values.put("test1", "abc");
values.put("test2", "xyz");
hbaseClient.addRecord("test", "Test", values);
}
}
The code as it currently is throws the following error:
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods. Those methods cannot be stubbed/verified.
2. inside when() you don't call method on mock but on some other object.
3. the parent of the mocked class is not public. It is a limitation of the mock engine.
at org.powermock.api.mockito.PowerMockito.when(PowerMockito.java:495)
at
com.rtn.cdp.storage.hbase.test.TestHBaseDataClient.setUpBeforeClass(TestHBaseClient.java:32)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498) at
org.junit.internal.runners.ClassRoadie.runBefores(ClassRoadie.java:56)
at
org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:43)
at
org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:118)
at
org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:101)
at
org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
at
org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:53)
at
org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at
org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)