From 9700a09c95b0e3bd2dcaf618ee768683a7dad400 Mon Sep 17 00:00:00 2001 From: Alex Gabor Date: Sat, 22 Jan 2022 11:15:18 +0200 Subject: [PATCH] Proof read core instruction set --- codekata/core.instructionset | 58 ++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/codekata/core.instructionset b/codekata/core.instructionset index c85b86a..8dfeb6d 100644 --- a/codekata/core.instructionset +++ b/codekata/core.instructionset @@ -2,7 +2,7 @@ In this testing instruction set you will learn how to write simple tests using mockito. -Every test will be around one class and all of it's dependencies will be mocked out. +Every test will be around one class and all of its dependencies will be mocked out. Also suspend functions will be tested so you will see how to do that as well. I would suggest to open this document in your browser, while working in Android Studio. @@ -15,7 +15,7 @@ I would suggest to open this document in your browser, while working in Android org.fnives.test.showcase.core.session.SessionExpirationAdapter ``` - As you can see it's a simple adapter between an interface and it's received parameter. + As you can see it's a simple adapter between an interface and its received parameter. - Now navigate to the test class: @@ -32,8 +32,8 @@ private lateinit var sut: SessionExpirationAdapter // System Under Testing private lateinit var mockSessionExpirationListener: SessionExpirationListener ``` -Now we need to initialize it, create a method names `setUp` and annotate it with `@BeforeEach` -and initialize the `sut` variable, we will see that the adapter expects a constructor argument +Now we need to initialize it. Create a method named `setUp` and annotate it with `@BeforeEach` +and initialize the `sut` variable. We will see that the adapter expects a constructor argument. ```kotlin @BeforeEach // this means this method will be invoked before each test in this class @@ -43,8 +43,8 @@ fun setUp() { } ``` -Great, now what is that mock? Simply put, it's a empty implementation of the interface. We can manipulate -that mock object to return what we want and verify it's method calls. +Great, now what is that mock? Simply put, it's an empty implementation of the interface. We can manipulate +that mock object to return what we want and verify its method calls. ### 2. First simple test @@ -105,25 +105,25 @@ Now this is a bit more complicated, let's open our test file: org.fnives.test.showcase.core.login.CodeKataSecondLoginUseCaseTest ``` -- declare the `sut` variable and it's dependencies, you should be familiar how to do this by now. +- declare the `sut` variable and its dependencies, you should be familiar how to do this by now. ### 1. `emptyUserNameReturnsLoginStatusError` -now let's write our first test: `emptyUserNameReturnsLoginStatusError` +Now let's write our first test: `emptyUserNameReturnsLoginStatusError` -first we declare what kind of result we expect: +First we declare what kind of result we expect: ```kotlin val expected = Answer.Success(LoginStatus.INVALID_USERNAME) ``` -next we do the actual invokation: +Next we do the actual invocation: ```kotlin val actual = sut.invoke(LoginCredentials("", "a")) ``` -lastly we add verification: +Lastly we add verification: ```kotlin Assertions.assertEquals(expected, actual) // assert the result is what we expected @@ -228,7 +228,7 @@ Now we see how we can mock responses. ### 4. `validResponseResultsInSavingSessionAndSuccessReturned`, -Now continue with `validResponseResultsInSavingSessionAndSuccessReturned`, You should have almost every tool to do this test: +Now continue with `validResponseResultsInSavingSessionAndSuccessReturned`. You should have almost every tool to do this test: - declare the expected value - do the mock response - call the system under test @@ -255,15 +255,15 @@ fun validResponseResultsInSavingSessionAndSuccessReturned() = runTest { ### 5. `invalidResponseResultsInErrorReturned` -this is really similar to our previous test, however now somehow we have to mock throwing an exception +This is really similar to our previous test, however now somehow we have to mock throwing an exception -to do this let's create an exception: +To do this let's create an exception: ```kotlin val exception = RuntimeException() ``` -declare our expected value: +Declare our expected value: ```kotlin val expected = Answer.Error(UnexpectedException(exception)) @@ -275,13 +275,13 @@ Do the mocking: whenever(mockLoginRemoteSource.login(LoginCredentials("a", "b"))).doThrow(exception) ``` -invocation: +Invocation: ```kotlin val actual = sut.invoke(LoginCredentials("a", "b")) ``` -verification: +Verification: ```kotlin Assertions.assertEquals(expected, actual) @@ -292,7 +292,7 @@ verifyZeroInteractions(mockUserDataLocalStorage) - and the pattern of GIVEN-WHEN-THEN description. ``` -together: +Together: ```kotlin @DisplayName("GIVEN error resposne WHEN trying to login THEN session is not touched and error is returned") @@ -323,7 +323,7 @@ Additionally the Content is cached. So observing again should not yield loading. The inner workings of the class shouldn't matter, just the public apis, since that's what we want to test. -For setup we declare the system under test and it's mock argument. +For setup we declare the system under test and its mock argument. ```kotlin private lateinit var sut: ContentRepository @@ -368,7 +368,7 @@ Next the action: val actual = sut.contents.take(2).toList() ``` -Now just the verifications +Now just the verifications: ```kotlin Assertions.assertEquals(expected, actual) @@ -398,7 +398,7 @@ Assertions.assertEquals(expected, actual) ### 4. `verifyCaching` -Still sticking to just that function, we should verify it's caching behaviour, aka if a data was loaded once the next time we observe the flow that data is returned: +Still sticking to just that function, we should verify its caching behaviour, aka if a data was loaded once the next time we observe the flow that data is returned: The setup is similar to the happy flow, but take a look at the last line closely ```kotlin @@ -413,7 +413,7 @@ The action will only take one element which we expect to be the cache val actual = sut.contents.take(1).toList() ``` -In the verification state, we will also make sure the request indead was called only once: +In the verification state, we will also make sure the request indeed was called only once: ```kotlin verify(mockContentRemoteSource, times(1)).get() Assertions.assertEquals(expected, actual) @@ -421,7 +421,7 @@ Assertions.assertEquals(expected, actual) ### 5. `loadingIsShownBeforeTheRequestIsReturned` -So far we just expected the first element is "loading", but it could easily happen that the flow set up in such a way that the loading is not emitted +So far we just expected the first element is "loading", but it could easily happen that the flow is set up in such a way that the loading is not emitted before the request already finished. This can be an easy mistake with such flows, but would be really bad UX, so let's see how we can verify something like that: @@ -433,7 +433,7 @@ Generally we could still use mockito mocks OR we could create our own Mock. #### Creating our own mock. -We can simply implement the interface of ContentRemoteSource. Have a it's method suspend until a signal. +We can simply implement the interface of ContentRemoteSource. Have it's method suspend until a signal. Something along the way of: @@ -460,13 +460,13 @@ In this case we should recreate our sut in the test and feed it our own remote s To mock such behaviour with mockito with our current tool set is not as straight forward as creating our own. That's because how we used mockito so far it is not aware of the nature of suspend functions, like our code is in the custom mock. -However mockito give us the arguments passed into the function. +However mockito gives us the arguments passed into the function. And since we know the Continuation object is passed as a last argument in suspend functions we can take advantage of that. This then can be abstracted away and used wherever without needing to create Custom Mocks for every such case. To get arguments when creating a response for the mock you need to use thenAnswer { } and this lambda will receive InvocationOnMock containing the arguments. -Luckily this has already be done in "org.mockito.kotlin" and it's called `doSuspendableAnswer` +Luckily this has already been done in "org.mockito.kotlin" and it's called `doSuspendableAnswer` The point here is that we can get arguments while mocking with mockito, and we are able to extend it in a way that helps us in common patterns. @@ -502,7 +502,7 @@ suspendedRequest.complete(Unit) We still didn't even touch the fetch method so let's test the behaviour next: -We want to get the first result triggered by the subscription to the flow, and the again another loading and result after a call to `fetch`, so the setup would be: +We want to get the first result triggered by the subscription to the flow, and then again another loading and result after a call to `fetch`, so the setup would be: ```kotlin val exception = RuntimeException() val expected = listOf( @@ -531,7 +531,7 @@ Assertions.assertEquals(expected, actual.await()) ``` However this test will hang. This is because `runTest` uses by default `StandardTestDispatcher` which doesn't enter child coroutines immediately and the async block will only be executed after the call to fetch. -This is a good thing because it gives us more control over the order of execution and as a result our test are not shaky. +This is a good thing because it gives us more control over the order of execution and as a result our tests are not shaky. To make sure that `fetch` is called only when `take` suspends, we can call `advanceUntilIdle` which will give the opportunity of the async block to execute. So our test becomes: ```kotlin @@ -553,7 +553,7 @@ fun whenFetchingRequestIsCalledAgain() = runTest(UnconfinedTestDispatcher()) { } ``` -Now we can test even complicated interactions between methods and classes with TestCoroutineDispatcher. +Now we can test even complicated interactions between methods and classes with test dispatchers. ### 7. `noAdditionalItemsEmitted`