-1

I am setting up a rest service in go using MongoDB and mux routers. I am running into issues on how to best set this up to allow for unit/integration testing in a separate database.

I have tried setting up the database in the Init() function but this is giving me issues when trying to set up unit tests using a test DB.

Here are some examples of where I am at right now. I am trying to use a method to connect to the database before posting so in my test I can connect to a different testing database.

type user struct {
    name string `json:"name"`
    age  int    `json:"age"`
}

type database struct {
    db *mongo.Database
}

func ConnectToDB() (*database, error) {
    client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
    if err != nil {
        return nil, err
    }

    if err := client.Connect(context.Background()); err != nil {
        return nil, err
    }

    database := &database{
        db: client.Database("PMBaseGo"),
    }

    return database, nil
}

func PostUser(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    //Retrieving request body
    var user user
    _ = json.NewDecoder(r.Body).Decode(&user)
    //Posting Company.
    err := PostUserToDB(user)
    //Error Handling
    if err != nil {
        fmt.Println(err)
        w.WriteHeader(500)
    }
}

func (d *database) connPostUserToDB(user user) error {
    _, err := d.db.Collection("companies").InsertOne(context.Background(), user)
    if err != nil {
        return err
    }
    return nil
}

func main() {
    _, _ = ConnectToDB()
    r := mux.NewRouter()

    r.HandleFunc("/user", PostUser).Methods("POST")

    fmt.Println("Application Running...")
    log.Fatal(http.ListenAndServe("localhost:8081", r))
}

The issue I am at right now is trying to call the PostUserToDB method in the PostUser function.

I'm starting to think the issue lies prior to this in how I am going about connecting to the DB.

jmacnc
  • 157
  • 2
  • 7
  • 19
  • You need to design for testing. If you trace the code under test backward, it's hard-coded to call a particular implementation of getting a database connection, which is hard-coded to connect to a specific database DSN. This is effectively completely untestable (not to mention unusable in production if you want to connect to an external mongo server). – Adrian May 17 '19 at 16:01

1 Answers1

2

Make your handler a method on a struct that holds the handler's dependencies:

type server struct {
    srv *service
}

type service struct {
    db *mongo.Database
}

func (s *server) PostUser(w http.ResponseWriter, r *http.Request) {
    // Create user struct from request data...

    if err := s.srv.CreateUser(u); err != nil {
        // Error response.
    }

    // Success response.
}

func (s *service) CreateUser(u *user) error {
    // ...
    _, err := d.db.Collection("foo").InsertOne(r.Context(), user)
    return err
}

func main() {
    s := &server{
        srv: &service{
            db: getMongoDatabase(),
        },
    }

    r := mux.NewRouter()
    r.HandleFunc("/user", s.PostUser).Methods("POST")

    log.Fatal(http.ListenAndServe("localhost:8081", r))
}

In your test code, you can simply inject a different database to have your tests operate on that database.

Emile Pels
  • 3,837
  • 1
  • 15
  • 23
  • I think that is what I was trying to do with the separate calls. ```PostUser``` handled the HTTP request and that called "PostUsertoDB" which made the call to the DB. Your answer seemed to take that out and did everything in the HTTP function. I'm a bit confused now how to separate it while not running into my initial problem. – jmacnc May 17 '19 at 16:17
  • 1
    @jmacnc Initially I omitted that for brevity, but I've updated my answer to include an extra "service" layer in between – Emile Pels May 18 '19 at 15:42