diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogTest.kt
new file mode 100644
index 000000000000..c72a147b1ce1
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogTest.kt
@@ -0,0 +1,99 @@
+package net.mullvad.mullvadvpn.compose.screen
+
+import android.content.res.Resources
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTextInput
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkObject
+import io.mockk.verify
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import net.mullvad.mullvadvpn.compose.state.VoucherDialogUiState
+import net.mullvad.mullvadvpn.compose.test.VOUCHER_INPUT_TEST_TAG
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
+import net.mullvad.mullvadvpn.util.VoucherRegexHelper
+import net.mullvad.mullvadvpn.viewmodel.VoucherDialogViewModel
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class RedeemVoucherDialogTest {
+    @get:Rule val composeTestRule = createComposeRule()
+
+    private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
+    private val mockResources: Resources = mockk()
+
+    private val _connectionState =
+        MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
+
+    val connectionState = _connectionState.asStateFlow()
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this)
+        mockkObject(VoucherRegexHelper)
+    }
+
+    @Test
+    fun testInsertInvalidVoucher() {
+        // Arrange
+        every { VoucherRegexHelper.validate(any()) } returns false
+        every { mockServiceConnectionManager.connectionState } returns connectionState
+
+        // Act
+        val vm = VoucherDialogViewModel(mockServiceConnectionManager, mockResources)
+        val uiState = vm.uiState.value
+        composeTestRule.setContentWithTheme {
+            RedeemVoucherDialogScreen(
+                uiState = uiState,
+                onVoucherInputChange = { vm.onVoucherInputChange(it) },
+                onRedeem = {},
+                onDismiss = {}
+            )
+        }
+
+        // Sets the TextField value
+        composeTestRule.onNodeWithTag(VOUCHER_INPUT_TEST_TAG).performTextInput(DUMMY_VALID_VOUCHER)
+        composeTestRule.onNodeWithText(REDEEM_BUTTON_TEXT).performClick()
+
+        // Assert
+        composeTestRule.onNodeWithText(DUMMY_INVALID_VOUCHER).assertDoesNotExist()
+    }
+
+    @Test
+    fun testDismissDialog() {
+        // Arrange
+        val mockedClickHandler: (Boolean) -> Unit = mockk(relaxed = true)
+
+        // Act
+        composeTestRule.setContentWithTheme {
+            RedeemVoucherDialogScreen(
+                uiState = VoucherDialogUiState.INITIAL,
+                onVoucherInputChange = {},
+                onRedeem = {},
+                onDismiss = mockedClickHandler
+            )
+        }
+
+        composeTestRule.onNodeWithText(CANCEL_BUTTON_TEXT).performClick()
+
+        // Assert
+        verify { mockedClickHandler.invoke(false) }
+    }
+
+    companion object {
+        private const val REDEEM_BUTTON_TEXT = "Redeem"
+        private const val CANCEL_BUTTON_TEXT = "Cancel"
+        //        private const val DUMMY_VALID_VOUCHER = "DUMM-YVAL-IDVO-UCHE-R"
+        private const val DUMMY_VALID_VOUCHER = "DUMMYVALIDVOUCHER"
+        private const val DUMMY_USED_VOUCHER = "DUMM-YUSE-DVOU-CHER"
+        private const val DUMMY_INVALID_VOUCHER = "DUMM-YINV-ALID-VOUC-HER"
+    }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt
index 29d4807b42e0..b488f4013a6a 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt
@@ -14,6 +14,7 @@ import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.pluralStringResource
 import androidx.compose.ui.res.stringResource
@@ -29,6 +30,7 @@ import net.mullvad.mullvadvpn.compose.button.VariantButton
 import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorSmall
 import net.mullvad.mullvadvpn.compose.state.VoucherDialogState
 import net.mullvad.mullvadvpn.compose.state.VoucherDialogUiState
+import net.mullvad.mullvadvpn.compose.test.VOUCHER_INPUT_TEST_TAG
 import net.mullvad.mullvadvpn.compose.textfield.CustomTextField
 import net.mullvad.mullvadvpn.compose.util.MAX_VOUCHER_LENGTH
 import net.mullvad.mullvadvpn.compose.util.vouchersVisualTransformation
@@ -230,7 +232,9 @@ private fun EnterVoucherBody(
         keyboardType = KeyboardType.Password,
         placeholderText = stringResource(id = R.string.voucher_hint),
         visualTransformation = vouchersVisualTransformation(),
-        isDigitsOnlyAllowed = false
+        isDigitsOnlyAllowed = false,
+        isEnabled = true,
+        modifier = Modifier.testTag(VOUCHER_INPUT_TEST_TAG)
     )
     Spacer(modifier = Modifier.height(Dimens.smallPadding))
     Row(
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
index 01d434a117d0..e3cb4faa5b32 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
@@ -31,3 +31,6 @@ const val PLAY_PAYMENT_INFO_ICON_TEST_TAG = "play_payment_info_icon_test_tag"
 
 const val LOGIN_TITLE_TEST_TAG = "login_title_test_tag"
 const val LOGIN_INPUT_TEST_TAG = "login_input_test_tag"
+
+// VoucherDialog
+const val VOUCHER_INPUT_TEST_TAG = "voucher_input_test_tag"
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/utils/RedeemVoucherHelperTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/utils/RedeemVoucherHelperTest.kt
new file mode 100644
index 000000000000..517cc793e06d
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/utils/RedeemVoucherHelperTest.kt
@@ -0,0 +1,57 @@
+package net.mullvad.mullvadvpn.utils
+
+import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.util.VoucherRegexHelper
+import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class RedeemVoucherHelperTest(private val isValid: Boolean, private val voucher: String) {
+    @get:Rule val testCoroutineRule = TestCoroutineRule()
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters
+        fun data(): Collection<Array<Any>> =
+            listOf(
+                arrayOf(true, acceptable_inputs_for_voucher[0]),
+                arrayOf(true, acceptable_inputs_for_voucher[1]),
+                arrayOf(true, acceptable_inputs_for_voucher[2]),
+                arrayOf(true, acceptable_inputs_for_voucher[3]),
+                arrayOf(true, acceptable_inputs_for_voucher[4]),
+                arrayOf(true, acceptable_inputs_for_voucher[5]),
+                arrayOf(true, acceptable_inputs_for_voucher[6]),
+                arrayOf(true, acceptable_inputs_for_voucher[7]),
+                arrayOf(false, non_acceptable_inputs_for_voucher[0]),
+                arrayOf(false, non_acceptable_inputs_for_voucher[1]),
+                arrayOf(false, non_acceptable_inputs_for_voucher[2])
+            )
+
+        private val acceptable_inputs_for_voucher =
+            arrayOf(
+                "1",
+                "a",
+                "A",
+                "AAAA",
+                "AAAABBBB11112222",
+                "AAAA BBBB 1111 2222",
+                "AAAA-AAAA-1111-2222\r",
+                "AAAA-AAAA-1111-2222\n",
+            )
+        private val non_acceptable_inputs_for_voucher =
+            arrayOf(
+                "@",
+                "AAAABBBBCCCCDDDD\t",
+                "AAAA_BBBB_CCCC_DDDD",
+            )
+    }
+
+    @Test
+    fun shouldReturnExpectedResultForVoucherCheck() {
+        assertThat(VoucherRegexHelper.validate(voucher), equalTo(isValid))
+    }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt
index bf87799d876d..7f585467a27b 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt
@@ -1,22 +1,30 @@
 package net.mullvad.mullvadvpn.viewmodel
 
 import android.content.res.Resources
+import app.cash.turbine.turbineScope
 import io.mockk.coEvery
 import io.mockk.coVerify
 import io.mockk.every
 import io.mockk.mockk
 import io.mockk.mockkStatic
 import io.mockk.unmockkAll
+import kotlin.test.assertIs
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.compose.state.VoucherDialogState
 import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.model.VoucherSubmission
 import net.mullvad.mullvadvpn.model.VoucherSubmissionError
 import net.mullvad.mullvadvpn.model.VoucherSubmissionResult
+import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
 import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
 import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
 import net.mullvad.mullvadvpn.ui.serviceconnection.VoucherRedeemer
+import net.mullvad.mullvadvpn.ui.serviceconnection.voucherRedeemer
 import org.junit.After
+import org.junit.Assert
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 
@@ -24,10 +32,17 @@ class VoucherDialogViewModelTest {
     @get:Rule val testCoroutineRule = TestCoroutineRule()
 
     private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
-    private val mockVoucherRedeemer: VoucherRedeemer = mockk()
     private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
+    private val mockVoucherSubmission: VoucherSubmission = mockk()
+    private val mockConnectionProxy: ConnectionProxy = mockk()
+    private val serviceConnectionState =
+        MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
+
+    private val mockVoucherRedeemer: VoucherRedeemer = mockk()
     private val mockResources: Resources = mockk()
 
+    private val mockVoucherSubmissionOkResult: VoucherSubmissionResult.Ok =
+        VoucherSubmissionResult.Ok(mockVoucherSubmission)
     private val mockVoucherSubmissionErrorResult: VoucherSubmissionResult =
         VoucherSubmissionResult.Error(VoucherSubmissionError.OtherError)
 
@@ -36,9 +51,9 @@ class VoucherDialogViewModelTest {
     @Before
     fun setUp() {
         mockkStatic(CACHE_EXTENSION_CLASS)
-        every { mockServiceConnectionManager.connectionState.value.readyContainer() } returns
-            mockServiceConnectionContainer
+        every { mockServiceConnectionManager.connectionState } returns serviceConnectionState
         every { mockServiceConnectionContainer.voucherRedeemer } returns mockVoucherRedeemer
+        every { mockServiceConnectionContainer.connectionProxy } returns mockConnectionProxy
 
         viewModel =
             VoucherDialogViewModel(
@@ -53,21 +68,46 @@ class VoucherDialogViewModelTest {
     }
 
     @Test
-    @Ignore("TODO: Fix this failing test and then enable it again.")
-    fun test_submit_invalid_voucher() = runTest {
-        val voucher = DUMMY_VALID_VOUCHER
+    fun testSubmitVoucher() = runTest {
+        val voucher = DUMMY_INVALID_VOUCHER
         val dummyStringResource = DUMMY_STRING_RESOURCE
         // Arrange
+        serviceConnectionState.value =
+            ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+        every { mockServiceConnectionManager.voucherRedeemer() } returns mockVoucherRedeemer
         every { mockResources.getString(any()) } returns dummyStringResource
         coEvery { mockVoucherRedeemer.submit(voucher) } returns mockVoucherSubmissionErrorResult
         // Act, Assert
+        assertIs<VoucherDialogState.Default>(viewModel.uiState.value.voucherViewModelState)
         viewModel.onRedeem(voucher)
         coVerify(exactly = 1) { mockVoucherRedeemer.submit(voucher) }
     }
 
+    @Test
+    fun testInsertValidVoucher() = runTest {
+        turbineScope {
+            val voucher = DUMMY_VALID_VOUCHER
+            val dummyStringResource = DUMMY_STRING_RESOURCE
+            // Arrange
+            val uiStates = viewModel.uiState.testIn(backgroundScope)
+            every { mockServiceConnectionManager.voucherRedeemer() } returns mockVoucherRedeemer
+            every { mockResources.getString(any()) } returns dummyStringResource
+            every { mockVoucherSubmission.timeAdded } returns 0
+            coEvery { mockVoucherRedeemer.submit(voucher) } returns mockVoucherSubmissionOkResult
+
+            // Act, Assert
+            viewModel.onRedeem(voucher)
+            Assert.assertEquals(
+                VoucherDialogState.Default,
+                uiStates.awaitItem().voucherViewModelState
+            )
+        }
+    }
+
     companion object {
         private const val CACHE_EXTENSION_CLASS = "net.mullvad.mullvadvpn.util.CacheExtensionsKt"
-        private const val DUMMY_VALID_VOUCHER = "DUMMY_VALID_VOUCHER"
-        private const val DUMMY_STRING_RESOURCE = "DUMMY_STRING_RESOURCE"
+        private const val DUMMY_VALID_VOUCHER = "dummy_valid_voucher"
+        private const val DUMMY_INVALID_VOUCHER = "dummy_invalid_voucher"
+        private const val DUMMY_STRING_RESOURCE = "dummy_string_resource"
     }
 }