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

[Question]: Most functions from PostgreSQLBuilder are inline, making the class very hard to unit test. Do you have recommendations? #298

Closed
1 task done
jlengrand opened this issue Sep 27, 2023 · 5 comments
Assignees
Labels
question Further information is requested

Comments

@jlengrand
Copy link

General info

What is your question?

Hey there!

See this question for context.

I'm using the library and I want to unit test my code, reason for which I am trying to mock the SupabaseClient.

Thing is, because functions in PostgrestBuilder.kt are inlined they cannot be mocked by definitions, which makes it hard to unit test.

On top of this, because Supabase does not seem to offer test containers, I cannot turn myself to integration tests either.

Do you have any recommendations as to what to do?

Thanks!

@jlengrand jlengrand added the question Further information is requested label Sep 27, 2023
@jlengrand
Copy link
Author

BTW, I made a minimally reproduceable example over here : https://github.com/jlengrand/supabase-mock-demo-kotlin.

No need for any db or db credentials to run it

@jlengrand
Copy link
Author

My current method is to wrap your library in a class and mock that class instead, but it looks more like a workaround than a solution

https://stackoverflow.com/a/77188050/282677

@jan-tennert
Copy link
Collaborator

Hey! Sadly, I currently don't have a good solution for that problem. The only two solutions I can think of is your current approach and using a mocked http engine for ktor, which you can provide using

val client = createSupabaseClient(
    supabaseUrl,
    supabaseKey,
) {
    httpEngine = MockEngine { request -> //this will get executed everytime a ktor gets used by any function
        respond(Json.encodeToString(listOf<ResultPerson>()))
    }
}

But both obviously aren't ideal, and I'm not sure what I could do to make this easier.

@hieuwu
Copy link
Contributor

hieuwu commented Oct 5, 2023

My current method is to wrap your library in a class and mock that class instead, but it looks more like a workaround than a solution

https://stackoverflow.com/a/77188050/282677

Based on the question. Could you please try providing the Posgrest instance instead of the client to where you want to call the API? It would be easier to mock the dependency when write test

@jlengrand jlengrand changed the title [Question]: Most functions from PostgreSQLBuilder are inline, making the class very hard to unit test. Do you have reocmmendations? [Question]: Most functions from PostgreSQLBuilder are inline, making the class very hard to unit test. Do you have recommendations? Oct 11, 2023
@jlengrand
Copy link
Author

Coming back here just to mentioned that I decided to go another direction, which I'm pretty satisfied with actually. I created a simple Docker Compose setup that I run with test containers and which simulate a simple Supabase instance. No need for mocking any more. It's more "integration tests" than "unit tests" at that point though.

The full blog about it, but in short :

version: '3'

# Thanks https://github.com/mattddowney/compose-postgrest/blob/master/README.md
services:

  ################
  # postgrest-db #
  ################
  postgrest-db:
    image: postgres:16-alpine
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
      - DB_SCHEMA=${DB_SCHEMA}
    volumes:
      - "./initdb:/docker-entrypoint-initdb.d"
    networks:
      - postgrest-backend
    restart: always

  #############
  # postgrest #
  #############
  postgrest:
    image: postgrest/postgrest:latest
    ports:
      - "3000:3000"
    environment:
      - PGRST_DB_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgrest-db:5432/${POSTGRES_DB}
      - PGRST_DB_SCHEMA=${DB_SCHEMA}
      - PGRST_DB_ANON_ROLE=${DB_ANON_ROLE}
      - PGRST_JWT_SECRET=${PGRST_JWT_SECRET}
    networks:
      - postgrest-backend
    restart: always

  #############
  # Nginx     #
  #############
  nginx:
    image: nginx:alpine
    restart: always
    tty: true
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    ports:
      - "80:80"
      - "443:443"
    networks:
      - postgrest-backend

networks:
  postgrest-backend:
    driver: bridge

and the test file :

import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.createSupabaseClient
import io.github.jan.supabase.postgrest.Postgrest
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.testcontainers.containers.ComposeContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import java.io.File

@Testcontainers
class MainKtTestTestContainers {

    // The jwt token is calculated manually (https://jwt.io/) based on the private key in the docker-compose.yml file, and a payload of {"role":"postgres"} to match the user in the database
    private val jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMifQ.88jCdmcEuy2McbdwKPmuazNRD-dyD65WYeKIONDXlxg"

    private lateinit var supabaseClient: SupabaseClient

    @Container
    var environment: ComposeContainer =
        ComposeContainer(File("src/test/resources/docker-compose.yml"))
            .withExposedService("postgrest-db", 5432)
            .withExposedService("postgrest", 3000)
            .withExposedService("nginx", 80)

    @BeforeEach
    fun setUp() {
        val fakeSupabaseUrl = environment.getServiceHost("nginx", 80) +
                ":" + environment.getServicePort("nginx", 80)

        supabaseClient = createSupabaseClient(
            supabaseUrl = "http://$fakeSupabaseUrl",
            supabaseKey = jwtToken
        ) {
            install(Postgrest)
        }
    }

    @Test
    fun testEmptyPersonTable(){
        runBlocking {
            val result = getPerson(supabaseClient)
            assertEquals(0, result.size)
        }
    }

    @Test
    fun testSavePersonAndRetrieve(){
        val randomPersons = listOf(Person("Jan", 30), Person("Jane", 42))

        runBlocking {
            val result = savePerson(randomPersons, supabaseClient)
            assertEquals(2, result.size)
            assertEquals(randomPersons, result.map { it.toPerson() })

            val fetchResult = getPerson(supabaseClient)
            assertEquals(2, fetchResult.size)
            assertEquals(randomPersons, fetchResult.map { it.toPerson() })
        }
    }
}

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

No branches or pull requests

3 participants