Unit Testing for REST APIs in Go

Unit Testing for REST APIs in Go

Building RESTful APIs in different languages with different approaches and design patterns have always been as trending as being on a harder learning curve. This is due to focus on a lot of abstraction in code, the pain to get the project started and many more reasons. Upon that, to write test cases for the implemented services is also a pain in the neck.

Building RESTful APIs in different languages with different approaches and design patterns have always been as trending as being on a harder learning curve. This is due to focus on a lot of abstraction in code, the pain to get the project started and many more reasons. Upon that, to write test cases for the implemented services is also a pain in the neck.

Go gives you the privilege to write REST APIs in a very easy, elegant and concise way. In addition to that, Unit Testing in Go is also very easy and one command to hit to run the test cases.

As this article is just about writing unit test cases in Go, I would assume that you would know how to write REST implementation in Go.
To get a deeper insight into this simple assignment, you can look into this example.

I have attached the postman collection for an easy import, sql dump and also has an attached readme file so that you can get started with the simple assignment.

So let's begin-

It would be better if we take into consideration an example to write the test cases. Let's say we want to have an Online Address Book application where you create an address book, in which you have the following fields —

package main

type entry struct {
	ID           int    `json:"id,omitempty"`
	FirstName    string `json:"first_name,omitempty"`
	LastName     string `json:"last_name,omitempty"`
	EmailAddress string `json:"email_address,omitempty"`
	PhoneNumber  string `json:"phone_number,omitempty"`
}

We are going to assume that we have endpoints for GetEntries , GetEntryByID , CreateEntryUpdateEntry andDeleteEntry .

GetEntries -> "/entries" -> Method **GET**

GetEntryByID -> "/entry?id=1234" -> Method **GET**

CreateEntry -> "/entry" -> Method **POST**

UpdateEntry -> "/entry" -> Method **PUT**

DeleteEntry -> "/entry" -> Method **DELETE**Packages to use for Unit Testing in Go

  1. [testing](https://golang.org/pkg/testing/ "testing") — This is an in-built Golang package which is used to implement and run unit/automated test cases. It is intended to work with go testcommand with optional parameters to accept the go file to be tested.
  2. [net/http/httptest](https://golang.org/pkg/net/http/httptest/ "net/http/httptest") — This is also an in-built Golang package which provides privileges to do HTTP testing. In our case, we would like to record response from the endpoints and do respective checks.

As this article is just about writing unit test cases in Go, I would assume that you would know how to write REST implementation in Go.### Writing Unit tests

Writing unit test cases in Go can be in same package or in different packages. There are 2 criteria through which the Go testing package identifies the test cases.

  • The file name should end with _test . For example — endpoints_test.go
  • The test cases function should start with Test word. For example -
func TestGetEntries(t *testing.T) { 
....
}

Writing Unit Test Cases for REST API endpoints

Let us take each endpoint one by one to see how can we test all the endpoints from the above example specified, i.e, GetEntriesGetEntryByID , GetEntryByIDNotFound , CreateEntry , EditEntry and DeleteEntry

Let us start with writing test cases for the following -

GetEntries Test Case—

func TestGetEntries(t *testing.T) {
	req, err := http.NewRequest("GET", "/entries", nil)
	if err != nil {
		t.Fatal(err)
	}
	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(GetEntries)
	handler.ServeHTTP(rr, req)
	if status := rr.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}

	// Check the response body is what we expect.
	expected := `[{"id":1,"first_name":"Krish","last_name":"Bhanushali","email_address":"[email protected]","phone_number":"0987654321"},{"id":2,"first_name":"xyz","last_name":"pqr","email_address":"[email protected]","phone_number":"1234567890"},{"id":6,"first_name":"FirstNameSample","last_name":"LastNameSample","email_address":"[email protected]","phone_number":"1111111111"}]`
	if rr.Body.String() != expected {
		t.Errorf("handler returned unexpected body: got %v want %v",
			rr.Body.String(), expected)
	}
}

Note: The Github repository specified above does not contain a separate go file for each test case. I have specified all test cases in one go file.

Let us go through the get_entries_test.go line by line —

  • The file name should end with _test . For example — endpoints_test.go
  • The test cases function should start with Test word. For example -

GetEntryByID Test Case-

func TestGetEntryByID(t *testing.T) {

	req, err := http.NewRequest("GET", "/entry", nil)
	if err != nil {
		t.Fatal(err)
	}
	q := req.URL.Query()
	q.Add("id", "1")
	req.URL.RawQuery = q.Encode()
	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(GetEntryByID)
	handler.ServeHTTP(rr, req)
	if status := rr.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}

	// Check the response body is what we expect.
	expected := `{"id":1,"first_name":"Krish","last_name":"Bhanushali","email_address":"[email protected]","phone_number":"0987654321"}`
	if rr.Body.String() != expected {
		t.Errorf("handler returned unexpected body: got %v want %v",
			rr.Body.String(), expected)
	}
}

GetEntryByIDNotFound Test Case-

func TestGetEntryByIDNotFound(t *testing.T) {
	req, err := http.NewRequest("GET", "/entry", nil)
	if err != nil {
		t.Fatal(err)
	}
	q := req.URL.Query()
	q.Add("id", "123")
	req.URL.RawQuery = q.Encode()
	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(GetEntryByID)
	handler.ServeHTTP(rr, req)
	if status := rr.Code; status == http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusBadRequest)
	}
}

CreateEntry Test Case-

func TestCreateEntry(t *testing.T) {

	var jsonStr = []byte(`{"id":4,"first_name":"xyz","last_name":"pqr","email_address":"[email protected]","phone_number":"1234567890"}`)

	req, err := http.NewRequest("POST", "/entry", bytes.NewBuffer(jsonStr))
	if err != nil {
		t.Fatal(err)
	}
	req.Header.Set("Content-Type", "application/json")
	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(CreateEntry)
	handler.ServeHTTP(rr, req)
	if status := rr.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}
	expected := `{"id":4,"first_name":"xyz","last_name":"pqr","email_address":"[email protected]","phone_number":"1234567890"}`
	if rr.Body.String() != expected {
		t.Errorf("handler returned unexpected body: got %v want %v",
			rr.Body.String(), expected)
	}
}

Let us go through create_entry_test.go line by line-

Line 1–8 are same as the above description where the input is a jsonStrwhich is a JSON string of the entry object and we create a new request with a new method POST .

Line 9- Sets the header with the Content-Type to be application/json

Line 9–22- Again, the same thing where the request is sent out and the response is being compared with the expected. If the request sent out is not 200 OK or the actual response is not equal to the expected response then the test case fails.

EditEntry Test Case-

func TestEditEntry(t *testing.T) {

	var jsonStr = []byte(`{"id":4,"first_name":"xyz change","last_name":"pqr","email_address":"[email protected]","phone_number":"1234567890"}`)

	req, err := http.NewRequest("PUT", "/entry", bytes.NewBuffer(jsonStr))
	if err != nil {
		t.Fatal(err)
	}
	req.Header.Set("Content-Type", "application/json")
	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(UpdateEntry)
	handler.ServeHTTP(rr, req)
	if status := rr.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}
	expected := `{"id":4,"first_name":"xyz change","last_name":"pqr","email_address":"[email protected]","phone_number":"1234567890"}`
	if rr.Body.String() != expected {
		t.Errorf("handler returned unexpected body: got %v want %v",
			rr.Body.String(), expected)
	}
}

EditEntry test case is the same as CreateEntry test case except the request method is PUT for EditEntry .

DeleteEntry test case-

func TestDeleteEntry(t *testing.T) {
	req, err := http.NewRequest("DELETE", "/entry", nil)
	if err != nil {
		t.Fatal(err)
	}
	q := req.URL.Query()
	q.Add("id", "4")
	req.URL.RawQuery = q.Encode()
	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(DeleteEntry)
	handler.ServeHTTP(rr, req)
	if status := rr.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}
	expected := `{"id":4,"first_name":"xyz change","last_name":"pqr","email_address":"[email protected]","phone_number":"1234567890"}`
	if rr.Body.String() != expected {
		t.Errorf("handler returned unexpected body: got %v want %v",
			rr.Body.String(), expected)
	}
}

DeleteEntry Test case is again the same as GetEntryByID test case except the request method is DELETE for DeleteEntry .

Running the test cases

Lets start the server first, so that our test cases hit the endpoints by go run api.go entry.go keeping in consideration the example specified.

To run all test cases in a test suite you can do the following-

go test -v

Running the test suite

To run one test case, you can simply use do the following-

go test -v -run <Test Function Name>

Running one test case i.e., TestGetEntries

Hence, we can come to the conclusion here that we now know how to write unit test cases for RESTful APIs in Go.

Hope this helps.

Test a REST API with Java

Test a REST API with Java

This tutorial focuses on the basic principles and mechanics of testing a REST API with live Integration Tests (with a JSON payload).

This tutorial focuses on the basic principles and mechanics of testing a REST API with live Integration Tests (with a JSON payload).

1. Overview

The main goal is to provide an introduction to testing the basic correctness of the API – and we’re going to be using the latest version of the GitHub REST API for the examples.

For an internal application, this kind of testing will usually run as a late step in a Continuous Integration process, consuming the REST API after it has already been deployed.

When testing a REST resource, there are usually a few orthogonal responsibilities the tests should focus on:

  • the HTTP response code
  • other HTTP headers in the response
  • the payload (JSON, XML)

Each test should only focus on a single responsibility and include a single assertion. Focusing on a clear separation always has benefits, but when doing this kind of black box testing is even more important, as the general tendency is to write complex test scenarios in the very beginning.

Another important aspect of the integration tests is adherence to the Single Level of Abstraction Principle – the logic within a test should be written at a high level. Details such as creating the request, sending the HTTP request to the server, dealing with IO, etc should not be done inline but via utility methods.

2. Testing the Status Code
@Test
public void givenUserDoesNotExists_whenUserInfoIsRetrieved_then404IsReceived()
  throws ClientProtocolException, IOException {
  
    // Given
    String name = RandomStringUtils.randomAlphabetic( 8 );
    HttpUriRequest request = new HttpGet( "https://api.github.com/users/" + name );
 
    // When
    HttpResponse httpResponse = HttpClientBuilder.create().build().execute( request );
 
    // Then
    assertThat(
      httpResponse.getStatusLine().getStatusCode(),
      equalTo(HttpStatus.SC_NOT_FOUND));
}

This is a rather simple test – it verifies that a basic happy path is working, without adding too much complexity to the test suite.

If for whatever reason, it fails, then there is no need to look at any other test for this URL until this is fixed.

3. Testing the Media Type
@Test
public void
givenRequestWithNoAcceptHeader_whenRequestIsExecuted_thenDefaultResponseContentTypeIsJson()
  throws ClientProtocolException, IOException {
  
   // Given
   String jsonMimeType = "application/json";
   HttpUriRequest request = new HttpGet( "https://api.github.com/users/eugenp" );
 
   // When
   HttpResponse response = HttpClientBuilder.create().build().execute( request );
 
   // Then
   String mimeType = ContentType.getOrDefault(response.getEntity()).getMimeType();
   assertEquals( jsonMimeType, mimeType );
}

This ensures that the Response actually contains JSON data.

As you might have noticed, we’re following a logical progression of tests – first the Response Status Code (to ensure that the request was OK), then the Media Type of the Response, and only in the next test will we look at the actual JSON payload.

4. Testing the JSON Payload
@Test
public void
  givenUserExists_whenUserInformationIsRetrieved_thenRetrievedResourceIsCorrect()
  throws ClientProtocolException, IOException {
  
    // Given
    HttpUriRequest request = new HttpGet( "https://api.github.com/users/eugenp" );
 
    // When
    HttpResponse response = HttpClientBuilder.create().build().execute( request );
 
    // Then
    GitHubUser resource = RetrieveUtil.retrieveResourceFromResponse(
      response, GitHubUser.class);
    assertThat( "eugenp", Matchers.is( resource.getLogin() ) );
}

In this case, I know the default representation of GitHub resources is JSON, but usually, the Content-Type header of the response should be tested alongside the Accept header of the request – the client asks for a particular type of representation via Accept, which the server should honor.

5. Utilities for Testing

We’re going to use Jackson 2 to unmarshall the raw JSON String into a type-safe Java Entity:

public class GitHubUser {
 
    private String login;
 
    // standard getters and setters
}

We’re only using a simple utility to keep the tests clean, readable and at a high level of abstraction:

public static <T> T retrieveResourceFromResponse(HttpResponse response, Class<T> clazz)
  throws IOException {
  
    String jsonFromResponse = EntityUtils.toString(response.getEntity());
    ObjectMapper mapper = new ObjectMapper()
      .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    return mapper.readValue(jsonFromResponse, clazz);
}

Notice that Jackson is ignoring unknown properties that the GitHub API is sending our way – that’s simply because the Representation of a User Resource on GitHub gets pretty complex – and we don’t need any of that information here.

6. Dependencies

The utilities and tests make use of the following libraries, all available in Maven central:

7. Conclusion

This is only one part of what the complete integration testing suite should be. The tests focus on ensuring basic correctness for the REST API, without going into more complex scenarios,

For example, the following are not covered: Discoverability of the API, consumption of different representations for the same Resource, etc.

The implementation of all these examples and code snippets can be found over on Github – this is a Maven-based project, so it should be easy to import and run as it is.

Creating a Simple RESTful API in Go

Creating a Simple RESTful API in Go

In this tutorial demonstrates how you can create a simple RESTful JSON api using Go(Lang)

In this tutorial demonstrates how you can create a simple RESTful JSON api using Go(Lang)

If you are writing any form of web application, then you are most likely interfacing with 1 or more REST APIs in order to populate the dynamic parts of your application and to perform tasks such as updating or deleting data within a database.

In this tutorial, you are going to be building a fully-fledged REST API that exposes GET, POST, DELETE and PUT endpoints that will subsequently allow you to perform the full range of CRUD operations.

In order to keep this simple and focus on the basic concepts, we won’t be interacting with any backend database technologies to store the articles that we’ll be playing with. However, we will be writing this REST API in such a way that it will be easy to update the functions we will be defining so that they make subsequent calls to a database to perform any necessary CRUD operations.

Source Code - The full source code for this article can be found here: TutorialEdge/create-rest-api-in-go-tutorial## Prerequisites

  • You will need Go version 1.11+ installed on your development machine.
Goals

By the end of this tutorial, you will know how to create your own REST-ful APIs in **Go **that can handle all aspects of. You will know how to create **REST **endpoints within your project that can handle POST, GET, PUT and DELETE HTTP requests.

Video Tutorial REST Architectures

REST is everywhere these days, from websites to enterprise applications, the RESTful architecture style is a powerful way of providing communication between separate software components. Building REST APIs allow you to easily decouple both consumers and producers and are typically stateless by design.

Note - If you wish to learn more about the basics of REST APIs then check out What Are RESTful APIs?## JSON

For the purpose of this tutorial I’ll be using JavaScript Object Notation as a means of sending and receiving all information and thankfully Go comes with some excellent support for encoding and decoding these formats using the standard library package, encoding/json.

Note - For more information on the encoding/json package check out the official documentation: encoding/json## Marshalling

In order for us to easily We can easily convert data structures in GO into JSON by using something called marshalling which produces a byte slice containing a very long string with no extraneous white space.

Getting Started with A Basic API

To get started we will have to create a very simple server which can handle HTTP requests. To do this we’ll create a new file called main.go. Within this main.go file we’ll want to define 3 distinct functions. A homePage function that will handle all requests to our root URL, a handleRequests function that will match the URL path hit with a defined function and a main function which will kick off our API.

main.go

package main

import (
    "fmt"
    "log"
    "net/http"
)

func homePage(w http.ResponseWriter, r *http.Request){
    fmt.Fprintf(w, "Welcome to the HomePage!")
    fmt.Println("Endpoint Hit: homePage")
}

func handleRequests() {
    http.HandleFunc("/", homePage)
    log.Fatal(http.ListenAndServe(":10000", nil))
}

func main() {
    handleRequests()
}

If we run this on our machine now, we should see our very simple API start up on port 10000 if it’s not already been taken by another process. If we now navigate to <a href="http://localhost:10000/" target="_blank">http://localhost:10000/</a> in our local browser we should see Welcome to the HomePage! print out on our screen. This means we have successfully created the base from which we’ll build our REST API.

Note - If you want a more in-depth tutorial on how to create a go based web server then check out this tutorial here: Creating a Simple Web Server with Go(Lang)## Our Articles Structure

We’ll be creating a REST API that allows us to CREATE, READ, UPDATE and DELETE the articles on our website. When we talk about CRUD APIs we are referring to an API that can handle all of these tasks: Creating, Reading, Updating and Deleting.

Before we can get started, we’ll have to define our Article structure. Go has this concept of structs that are perfect for just this scenario. Let’s create an Article struct that features a Title, a Description (desc) and Content like so:

type Article struct {
    Title string `json:"Title"`
    Desc string `json:"desc"`
    Content string `json:"content"`
}

// let's declare a global Articles array
// that we can then populate in our main function
// to simulate a database
var Articles []Article

Our Struct contains the 3 properties we need to represent all of the articles on our site. In order for this to work, we’ll also have to import the "encoding/json" package into our list of imports.

Let’s now update our main function so that our Articles variable is populated with some dummy data that we can retrieve and modify later on.

func main() {
    articles := Articles{
        Article{Title: "Hello", Desc: "Article Description", Content: "Article Content"},
        Article{Title: "Hello 2", Desc: "Article Description", Content: "Article Content"},
    }
    handleRequests()
}

Perfect, let’s now move on to creating our /articles endpoint which will return all of the articles that we’ve just defined here.

Retrieving All Articles

In this part of the tutorial we are going to create a new REST endpoint which, when hit with a HTTP GET request, will return all of the articles for our site.

We’ll first start off by creating a new function called returnAllArticles, which will do the simple task of returning our newly populated Articles variable, encoded in JSON format:

main.go

func returnAllArticles(w http.ResponseWriter, r *http.Request){
    fmt.Println("Endpoint Hit: returnAllArticles")
    json.NewEncoder(w).Encode(Articles)
}

The call to json.NewEncoder(w).Encode(article) does the job of encoding our articles array into a JSON string and then writing as part of our response.

Before this will work, we’ll also need to add a new route to our handleRequests function that will map any calls to <a href="http://localhost:10000/articles" target="_blank">http://localhost:10000/articles</a> to our newly defined function.

func handleRequests() {
    http.HandleFunc("/", homePage)
    // add our articles route and map it to our 
    // returnAllArticles function like so
    http.HandleFunc("/articles", returnAllArticles)
    log.Fatal(http.ListenAndServe(":10000", nil))
}

Now that we’ve done this, run the code by typing go run main.go and then open up <a href="http://localhost:10000/articles" target="_blank">http://localhost:10000/articles</a> in your browser and you should see a JSON representation of your list of articles like so:

http://localhost:10000/articles response

[
  {
    Title: "Hello",
    desc: "Article Description",
    content: "Article Content"
  },
  {
    Title: "Hello 2",
    desc: "Article Description",
    content: "Article Content"
  }
];

We’ve successfully defined our first API endpoint.

In the next part of this series, you are going to update your REST API to use a gorilla/mux router instead of the traditional net/http router.

Swapping the routers will enable you to more easily perform tasks such as parsing any path or query parameters that may reside within an incoming HTTP request which we will need later on.

Getting Started with Routers

Now the standard library is adequate at providing everything you need to get your own simple REST API up and running but now that we’ve got the basic concepts down I feel it’s time to introduce third-party router packages. The most notable and highly used is the gorilla/mux router which, as it stands currently has 2,281 stars on Github.

Building our Router

We can update our existing main.go file and swap in a gorilla/mux based HTTP router in place of the standard library one which was present before.

Modify your handleRequests function so that it creates a new router.

main.go

package main

import (
    "fmt"
    "log"
    "net/http"
    "encoding/json"
    "github.com/gorilla/mux"
)

… // Existing code from above
func handleRequests() {
    // creates a new instance of a mux router
    myRouter := mux.NewRouter().StrictSlash(true)
    // replace http.HandleFunc with myRouter.HandleFunc
    myRouter.HandleFunc("/", homePage)
    myRouter.HandleFunc("/all", returnAllArticles)
    // finally, instead of passing in nil, we want
    // to pass in our newly created router as the second
    // argument
    log.Fatal(http.ListenAndServe(":10000", myRouter))
}

func main() {
    fmt.Println("Rest API v2.0 - Mux Routers")
    Articles = []Article{
        Article{Title: "Hello", Desc: "Article Description", Content: "Article Content"},
        Article{Title: "Hello 2", Desc: "Article Description", Content: "Article Content"},
    }
    handleRequests()
}

When you now run this, you will see no real change to the way our system works. It will still start up on the same port and return the same results depending on what endpoints you hit.

The only real difference is that we now have a gorilla/mux router which will allow us to easily do things such as retrieve path and query parameters later on in this tutorial.

$ go run main.go

Rest API v2.0 - Mux Routers

Path Variables

So far so good, we’ve created a very simple REST API that returns a homepage and all our Articles. But what happens if we want to just view one article?

Well, thanks to the gorilla mux router we can add variables to our paths and then pick and choose what articles we want to return based on these variables. Create a new route within your handleRequests() function just below our /articles route:

myRouter.HandleFunc("/article/{id}", returnSingleArticle)

Notice that we’ve added {id} to our path. This will represent our id variable that we’ll be able to use when we wish to return only the article that features that exact key. For now, our Article struct doesn’t feature an Id property. Let’s add that now:

type Article struct {
    Id      string `json:"Id"`
    Title   string `json:"Title"`
    Desc    string `json:"desc"`
    Content string `json:"content"`
}

We can then update our main function to populate our Id values in our Articles array:

func main() {
    Articles = []Article{
        Article{Id: "1", Title: "Hello", Desc: "Article Description", Content: "Article Content"},
        Article{Id: "2", Title: "Hello 2", Desc: "Article Description", Content: "Article Content"},
    }
    handleRequests()
}

Now that we’ve done that, in our returnSingleArticle function we can obtain this {id} value from our URL and we can return the article that matches this criteria. As we haven’t stored our data anywhere we’ll just be returning the Id that was passed to the browser.

func returnSingleArticle(w http.ResponseWriter, r *http.Request){
    vars := mux.Vars(r)
    key := vars["id"]

    fmt.Fprintf(w, "Key: " + key)
}

If we navigate to <a href="http://localhost:1000/article/1" target="_blank">http://localhost:1000/article/1</a>after we’ve now run this, you should see Key: 1 being printed out within the browser.

Let’s use this key value to return the specific article that matches that key.

func returnSingleArticle(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    key := vars["id"]

    // Loop over all of our Articles
    // if the article.Id equals the key we pass in
    // return the article encoded as JSON
    for _, article := range Articles {
        if article.Id == key {
            json.NewEncoder(w).Encode(article)
        }
    }
}

Run that by calling go run main.go and then open up <a href="http://localhost:10000/article/1" target="_blank">http://localhost:10000/article/1</a> in your browser:

http://localhost:10000/article/1 response

{
Id: "1",
Title: "Hello",
desc: "Article Description",
content: "Article Content"
}

You will now see the article matching the key 1 returned as JSON.

Creating and Updating Articles

In this part of the tutorial, you are going to build the Create, Update and DELETE part of a CRUD REST API. We have already covered the R with the ability to read both single articles and all articles.

Creating new Articles

Once again, you will need to create a new function which will do the job of creating this new article.

Let’s start off by creating a createNewArticle() function within our main.go file.

func createNewArticle(w http.ResponseWriter, r *http.Request) {
    // get the body of our POST request
    // return the string response containing the request body    
    reqBody, _ := ioutil.ReadAll(r.Body)
    fmt.Fprintf(w, "%+v", string(reqBody))
}

With this function defined, you can now add the route to the list of routes defined within the handleRequests function. This time however, we’ll be adding .Methods("POST") to the end of our route to specify that we only want to call this function when the incoming request is a HTTP POST request:

func handleRequests() {
    myRouter := mux.NewRouter().StrictSlash(true)
    myRouter.HandleFunc("/", homePage)
    myRouter.HandleFunc("/articles", returnAllArticles)
    // NOTE: Ordering is important here! This has to be defined before
    // the other `/article` endpoint. 
    myRouter.HandleFunc("/article", createNewArticle).Methods("POST")
    myRouter.HandleFunc("/article/{id}", returnSingleArticle)
    log.Fatal(http.ListenAndServe(":10000", myRouter))
}

Try running this again and then try submitting a HTTP POST request which contains the following POST body:

{
    "Id": "3", 
    "Title": "Newly Created Post", 
    "desc": "The description for my new post", 
    "content": "my articles content" 
}

Our endpoint will trigger and subsequently echo back whatever value was in the request body.

Now that you have validated your new endpoint is working correctly, let’s update our createNewArticle function so that it unmarshals the JSON in the request body into a new Article struct which can subsequently be appended to our Articles array:

func createNewArticle(w http.ResponseWriter, r *http.Request) {
    // get the body of our POST request
    // unmarshal this into a new Article struct
    // append this to our Articles array.    
    reqBody, _ := ioutil.ReadAll(r.Body)
    var article Article 
    json.Unmarshal(reqBody, &article)
    // update our global Articles array to include
    // our new Article
    Articles = append(Articles, article)

    json.NewEncoder(w).Encode(article)
}

Awesome! If you run this now and send the same POST request to your application, you will see that it echoes back the same JSON format as before, but it also appends the new Article to your Articles array.

Validate this now by hitting the <a href="http://localhost:10000/articles" target="_blank">http://localhost:10000/articles</a>:

http://localhost:10000/articles response

[
    {
        "Id": "1",
        "Title": "Hello",
        "desc": "Article Description",
        "content": "Article Content"
    },
    {
        "Id": "2",
        "Title": "Hello 2",
        "desc": "Article Description",
        "content": "Article Content"
    },
    {
        "Id": "3",
        "Title": "Newly Created Post",
        "desc": "The description for my new post",
        "content": "my articles content"
    }
]

You have now successfully added a Create function to your new REST API!

In the next section of this tutorial, you are going to look at how you can add a new API Endpoint which will allow you to delete Articles.

Deleting Articles

There may be times where you need to delete the data being exposed by your REST API. In order to do this, you need to expose a DELETE endpoint within your API that will take in an identifier and delete whatever is associated with that identifier.

In this section of this tutorial, you are going to be creating another endpoint which receives HTTP DELETE requests and deletes articles if they match the given Id path parameter.

Add a new function to your main.go file which we will call deleteArticle:

func deleteArticle(w http.ResponseWriter, r *http.Request) {
    // once again, we will need to parse the path parameters
    vars := mux.Vars(r)
    // we will need to extract the `id` of the article we
    // wish to delete
    id := vars["id"]

    // we then need to loop through all our articles
    for index, article := range Articles {
        // if our id path parameter matches one of our
        // articles
        if article.Id == id {
            // updates our Articles array to remove the 
            // article
            Articles = append(Articles[:index], Articles[index+1:]...)
        }
    }

}

Once again, you will need to add a route to the handleRequests function which maps to this new deleteArticle function:

func handleRequests() {
    myRouter := mux.NewRouter().StrictSlash(true)
    myRouter.HandleFunc("/", homePage)
    myRouter.HandleFunc("/articles", returnAllArticles)
    myRouter.HandleFunc("/article", createNewArticle).Methods("POST")
    // add our new DELETE endpoint here
    myRouter.HandleFunc("/article/{id}", deleteArticle).Methods("DELETE")
    myRouter.HandleFunc("/article/{id}", returnSingleArticle)
    log.Fatal(http.ListenAndServe(":10000", myRouter))
}

Try sending a new HTTP DELETE request to <a href="http://localhost:10000/article/2" target="_blank">http://localhost:10000/article/2</a>. This will delete the second article within your Articles array and when you subsequently hit <a href="http://localhost:10000/articles" target="_blank">http://localhost:10000/articles</a> with a HTTP GET request, you should see it now only contains a single Article.

Note - To keep this simple, we are updating a global variable. However, we aren’t doing any checks to ensure that our code is free of race conditions. In order to make this code thread-safe, I recommend checking out my other tutorial on Go Mutexes## Updating Articles Endpoint

The final endpoint you will need to implement is the Update endpoint. This endpoint will be a HTTP PUT based endpoint and will need to take in an Id path parameter, the same way we have done for our HTTP DELETE endpoint, as well as a JSON request body.

This JSON in the body of the incoming HTTP PUT request will contain the newer version of the article that we want to update.

Challenge

Try create an updateArticle function and corresponding route in the handleRequests function. This will match to PUT requests. Once you have this, implement the updateArticle function so that it parses the HTTP request body, using the same code that you used in your createNewArticle function.

Finally, you will have to loop over the articles in your Articles array and match and subsequently update the article.

Conclusion

This example represents a very simple RESTful API written using Go. In a real project, we’d typically tie this up with a database so that we were returning real values.

Test Driven Development of a Django RESTful API

Test Driven Development of a Django RESTful API

This post walks through the process of developing a CRUD-based RESTful API with Django and Django REST Framework, which is used for rapidly building RESTful APIs based on Django models.

This post walks through the process of developing a CRUD-based RESTful API with Django and Django REST Framework, which is used for rapidly building RESTful APIs based on Django models.

This application uses:

  • Python v3.6.0
  • Django v1.11.0
  • Django REST Framework v3.6.2
  • Postgres v9.6.1
  • Psycopg2 v2.7.1
Objectives

By the end of this tutorial you will be able to…

  1. Discuss the benefits of using Django REST Framework for bootstrapping the development of a RESTful API
  2. Validate model querysets using serializers
  3. Appreciate Django REST Framework’s Browsable API feature for a cleaner and well-documented version of your APIs
  4. Practice test-driven development
Why Django REST Framework?

Django REST Framework (REST Framework) provides a number of powerful features out-of-the-box that go well with idiomatic Django, including:

  1. Browsable API: Documents your API with a human-friendly HTML output, providing a beautiful form-like interface for submitting data to resources and fetching from them using the standard HTTP methods.
  2. Auth Support: REST Framework has rich support for various authentication protocols along with permissions and throttling policies which can be configured on a per-view basis.
  3. Serializers: Serializers are an elegant way of validating model querysets/instances and converting them to native Python datatypes that can be easily rendered into JSON and XML.
  4. Throttling: Throttling is way to determine whether a request is authorized or not and can be integrated with different permissions. It is generally used for rate limiting API requests from a single user.

Plus, the documentation is easy to read and full of examples. If you’re building a RESTful API where you have a one-to-one relationship between your API endpoints and your models, then REST Framework is the way to go.

Django Project Setup

Create and activate a virtualenv:

$ mkdir django-puppy-store
$ cd django-puppy-store
$ python3.6 -m venv env
$ source env/bin/activate

Install Django and set up a new project:

(env)$ pip install django==1.11.0
(env)$ django-admin startproject puppy_store

Your current project structure should look like this:

└── puppy_store
    ├── manage.py
    └── puppy_store
        ├── __init__.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py

Django App and REST Framework Setup

Start by creating the puppies app and installing REST Framework inside your virtualenv:

(env)$ cd puppy_store
(env)$ python manage.py startapp puppies
(env)$ pip install djangorestframework==3.6.2

Now we need to configure our Django project to make use of REST Framework.

First, add the puppies app and rest_framework to the INSTALLED_APPS section within puppy_store/puppy_store/settings.py:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'puppies',
    'rest_framework'
]

Next, define global settings for REST Framework in a single dictionary, again, in the settings.py file:

REST_FRAMEWORK = {
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    'DEFAULT_PERMISSION_CLASSES': [],
    'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}

This allows unrestricted access to the API and sets the default test format to JSON for all requests.

NOTE: Unrestricted access is fine for local development, but in a production environment you may need to restrict access to certain endpoints. Make sure to update this. Review the docs for more information.
Your current project structure should now look like:

└── puppy_store
    ├── manage.py
    ├── puppies
    │   ├── __init__.py
    │   ├── admin.py
    │   ├── apps.py
    │   ├── migrations
    │   │   └── __init__.py
    │   ├── models.py
    │   ├── tests.py
    │   └── views.py
    └── puppy_store
        ├── __init__.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py

Database and Model Setup

Let’s set up the Postgres database and apply all the migrations to it.

NOTE: Feel free to swap out Postgres for the relational database of your choice!
Once you have a working Postgres server on your system, open the Postgres interactive shell and create the database:

$ psql
# CREATE DATABASE puppy_store_drf;
CREATE DATABASE
# \q

Install psycopg2 so that we can interact with the Postgres server via Python:

(env)$ pip install psycopg2==2.7.1

Update the database configuration in settings.py, adding the appropriate username and password:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'puppy_store_drf',
        'USER': '<your-user>',
        'PASSWORD': '<your-password>',
        'HOST': '127.0.0.1',
        'PORT': '5432'
    }
}

Next, define a puppy model with some basic attributes in django-puppy-store/puppy_store/puppies/models.py:

from django.db import models


class Puppy(models.Model):
    """
    Puppy Model
    Defines the attributes of a puppy
    """
    name = models.CharField(max_length=255)
    age = models.IntegerField()
    breed = models.CharField(max_length=255)
    color = models.CharField(max_length=255)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def get_breed(self):
        return self.name + ' belongs to ' + self.breed + ' breed.'

    def __repr__(self):
        return self.name + ' is added.'

Now apply the migration:

(env)$ python manage.py makemigrations
(env)$ python manage.py migrate

Sanity Check

Hop into psql again and verify that the puppies_puppy has been created:

$ psql
# \c puppy_store_drf
You are now connected to database "puppy_store_drf".
puppy_store_drf=# \dt
                      List of relations
 Schema |            Name            | Type  |     Owner
--------+----------------------------+-------+----------------
 public | auth_group                 | table | michael.herman
 public | auth_group_permissions     | table | michael.herman
 public | auth_permission            | table | michael.herman
 public | auth_user                  | table | michael.herman
 public | auth_user_groups           | table | michael.herman
 public | auth_user_user_permissions | table | michael.herman
 public | django_admin_log           | table | michael.herman
 public | django_content_type        | table | michael.herman
 public | django_migrations          | table | michael.herman
 public | django_session             | table | michael.herman
 public | puppies_puppy              | table | michael.herman
(11 rows)

NOTE: You can run \d+ puppies_puppy if you want to look at the actual table details.
Before moving on, let’s write a quick unit test for the Puppy model.

Add the following code to a new file called test_models.py in a new folder called “tests” within “django-puppy-store/puppy_store/puppies”:

from django.test import TestCase
from ..models import Puppy


class PuppyTest(TestCase):
    """ Test module for Puppy model """

    def setUp(self):
        Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        Puppy.objects.create(
            name='Muffin', age=1, breed='Gradane', color='Brown')

    def test_puppy_breed(self):
        puppy_casper = Puppy.objects.get(name='Casper')
        puppy_muffin = Puppy.objects.get(name='Muffin')
        self.assertEqual(
            puppy_casper.get_breed(), "Casper belongs to Bull Dog breed.")
        self.assertEqual(
            puppy_muffin.get_breed(), "Muffin belongs to Gradane breed.")

In the above test, we added dummy entries into our puppy table via the setUp() method from django.test.TestCase and asserted that the get_breed() method returned the correct string.

Add an init.py file to “tests” and remove the tests.py file from “django-puppy-store/puppy_store/puppies”.

Let’s run our first test:

(env)$ python manage.py test
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.007s

OK
Destroying test database for alias 'default'...

Great! Our first unit test has passed!

Serializers

Before moving on to creating the actual API, let’s define a serializer for our Puppy model which validates the model querysets and produces Pythonic data types to work with.

Add the following snippet to django-puppy-store/puppy_store/puppies/serializers.py:

from rest_framework import serializers
from .models import Puppy


class PuppySerializer(serializers.ModelSerializer):
    class Meta:
        model = Puppy
        fields = ('name', 'age', 'breed', 'color', 'created_at', 'updated_at')

In the above snippet we defined a ModelSerializer for our puppy model, validating all the mentioned fields. In short, if you have a one-to-one relationship between your API endpoints and your models - which you probably should if you’re creating a RESTful API - then you can use a ModelSerializer to create a Serializer.

With our database in place, we can now start building the RESTful API…

RESTful Structure

In a RESTful API, endpoints (URLs) define the structure of the API and how end users access data from our application using the HTTP methods - GET, POST, PUT, DELETE. Endpoints should be logically organized around collections and elements, both of which are resources.

In our case, we have one single resource, puppies, so we will use the following URLS - /puppies/ and /puppies/<id> for collections and elements, respectively:

Routes and Testing (TDD)

We will be taking a test-first approach rather than a thorough test-driven approach, wherein we will be going through the following process:

  • add a unit test, just enough code to fail
  • then update the code to make it pass the test.

Once the test passes, start over with the same process for the new test.

Begin by creating a new file, django-puppy-store/puppy_store/puppies/tests/test_views.py, to hold all the tests for our views and create a new test client for our app:

import json
from rest_framework import status
from django.test import TestCase, Client
from django.urls import reverse
from ..models import Puppy
from ..serializers import PuppySerializer


# initialize the APIClient app
client = Client()

Before starting with all the API routes, let’s first create a skeleton of all view functions that return empty responses and map them with their appropriate URLs within the django-puppy-store/puppy_store/puppies/views.py file:

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Puppy
from .serializers import PuppySerializer


@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
    try:
        puppy = Puppy.objects.get(pk=pk)
    except Puppy.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    # get details of a single puppy
    if request.method == 'GET':
        return Response({})
    # delete a single puppy
    elif request.method == 'DELETE':
        return Response({})
    # update details of a single puppy
    elif request.method == 'PUT':
        return Response({})


@api_view(['GET', 'POST'])
def get_post_puppies(request):
    # get all puppies
    if request.method == 'GET':
        return Response({})
    # insert a new record for a puppy
    elif request.method == 'POST':
        return Response({})

Create the respective URLs to match the views in django-puppy-store/puppy_store/puppies/urls.py:

from django.conf.urls import url
from . import views


urlpatterns = [
    url(
        r'^api/v1/puppies/(?P<pk>[0-9]+)
Update *django-puppy-store/puppy_store/puppy_store/urls.py* as well:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
url(r'^', include('puppies.urls')),
url(
r'^api-auth/',
include('rest_framework.urls', namespace='rest_framework')
),
url(r'^admin/', admin.site.urls),
]

## Browsable API

With all routes now wired up with the view functions, let’s open up REST Framework’s Browsable API interface and verify whether all the URLs are working as expected.

First, fire up the development server:

(env)$ python manage.py runserver


Make sure to comment out all the attributes in ```REST_FRAMEWORK``` section of our ```settings.py``` file, to bypass login. Now visit ```<a href="http://localhost:8000/api/v1/puppies" target="_blank">http://localhost:8000/api/v1/puppies</a>```

You will see an interactive HTML layout for the API response. Similarly we can test the other URLs and verify all URLs are working perfectly fine.

Let’s start with our unit tests for each route.



## Routes
### GET ALL

Start with a test to verify the fetched records:

class GetAllPuppiesTest(TestCase):
""" Test module for GET all puppies API """

def setUp(self):
    Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    Puppy.objects.create(
        name='Muffin', age=1, breed='Gradane', color='Brown')
    Puppy.objects.create(
        name='Rambo', age=2, breed='Labrador', color='Black')
    Puppy.objects.create(
        name='Ricky', age=6, breed='Labrador', color='Brown')

def test_get_all_puppies(self):
    # get API response
    response = client.get(reverse('get_post_puppies'))
    # get data from db
    puppies = Puppy.objects.all()
    serializer = PuppySerializer(puppies, many=True)
    self.assertEqual(response.data, serializer.data)
    self.assertEqual(response.status_code, status.HTTP_200_OK)

Run the test. You should see the following error:

self.assertEqual(response.data, serializer.data)
AssertionError: {} != [OrderedDict([('name', 'Casper'), ('age',[687 chars])])]


Update the view to get the test to pass.

@api_view(['GET', 'POST'])
def get_post_puppies(request):
# get all puppies
if request.method == 'GET':
puppies = Puppy.objects.all()
serializer = PuppySerializer(puppies, many=True)
return Response(serializer.data)
# insert a new record for a puppy
elif request.method == 'POST':
return Response({})


Here, we get all the records for puppies and validate each using the ```PuppySerializer```.

Run the tests to ensure they all pass:

Ran 2 tests in 0.072s

OK

### GET Single

Fetching a single puppy involves two test cases:
1. Get valid puppy - e.g., the puppy exists
1. Get invalid puppy - e.g., the puppy does not exists

Add the tests:

class GetSinglePuppyTest(TestCase):
""" Test module for GET single puppy API """

def setUp(self):
    self.casper = Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    self.muffin = Puppy.objects.create(
        name='Muffin', age=1, breed='Gradane', color='Brown')
    self.rambo = Puppy.objects.create(
        name='Rambo', age=2, breed='Labrador', color='Black')
    self.ricky = Puppy.objects.create(
        name='Ricky', age=6, breed='Labrador', color='Brown')

def test_get_valid_single_puppy(self):
    response = client.get(
        reverse('get_delete_update_puppy', kwargs={'pk': self.rambo.pk}))
    puppy = Puppy.objects.get(pk=self.rambo.pk)
    serializer = PuppySerializer(puppy)
    self.assertEqual(response.data, serializer.data)
    self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_get_invalid_single_puppy(self):
    response = client.get(
        reverse('get_delete_update_puppy', kwargs={'pk': 30}))
    self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

Run the tests. You should see the following error:

self.assertEqual(response.data, serializer.data)
AssertionError: {} != {'name': 'Rambo', 'age': 2, 'breed': 'Labr[109 chars]26Z'}


Update the view:

@api_view(['GET', 'UPDATE', 'DELETE'])
def get_delete_update_puppy(request, pk):
try:
puppy = Puppy.objects.get(pk=pk)
except Puppy.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

# get details of a single puppy
if request.method == 'GET':
    serializer = PuppySerializer(puppy)
    return Response(serializer.data)

In the above snippet, we get the puppy using an ID. Run the tests to ensure they all pass.



### POST

Inserting a new record involves two cases as well:
1. Inserting a valid puppy
1. Inserting a invalid puppy

First, write tests for it:

class CreateNewPuppyTest(TestCase):
""" Test module for inserting a new puppy """

def setUp(self):
    self.valid_payload = {
        'name': 'Muffin',
        'age': 4,
        'breed': 'Pamerion',
        'color': 'White'
    }
    self.invalid_payload = {
        'name': '',
        'age': 4,
        'breed': 'Pamerion',
        'color': 'White'
    }

def test_create_valid_puppy(self):
    response = client.post(
        reverse('get_post_puppies'),
        data=json.dumps(self.valid_payload),
        content_type='application/json'
    )
    self.assertEqual(response.status_code, status.HTTP_201_CREATED)

def test_create_invalid_puppy(self):
    response = client.post(
        reverse('get_post_puppies'),
        data=json.dumps(self.invalid_payload),
        content_type='application/json'
    )
    self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Run the tests. You should see two failures:

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 200 != 400

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
AssertionError: 200 != 201


Again, update the view to get the tests to pass:

@api_view(['GET', 'POST'])
def get_post_puppies(request):
# get all puppies
if request.method == 'GET':
puppies = Puppy.objects.all()
serializer = PuppySerializer(puppies, many=True)
return Response(serializer.data)
# insert a new record for a puppy
if request.method == 'POST':
data = {
'name': request.data.get('name'),
'age': int(request.data.get('age')),
'breed': request.data.get('breed'),
'color': request.data.get('color')
}
serializer = PuppySerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


Here, we inserted a new record by serializing and validating the request data before inserting to the database.

Run the tests again to ensure they pass.

You can also test this out with the Browsable API. Fire up the development server again, and navigate to [http://localhost:8000/api/v1/puppies/](http://localhost:8000/api/v1/puppies/ "http://localhost:8000/api/v1/puppies/"). Then, within the POST form, submit the following as ```application/json```:

{
"name": "Muffin",
"age": 4,
"breed": "Pamerion",
"color": "White"
}


Be sure the GET ALL and Get Single work as well.



### PUT

Start with a test to update a record. Similar to adding a record, we again need to test for both valid and invalid updates:

class UpdateSinglePuppyTest(TestCase):
""" Test module for updating an existing puppy record """

def setUp(self):
    self.casper = Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    self.muffin = Puppy.objects.create(
        name='Muffy', age=1, breed='Gradane', color='Brown')
    self.valid_payload = {
        'name': 'Muffy',
        'age': 2,
        'breed': 'Labrador',
        'color': 'Black'
    }
    self.invalid_payload = {
        'name': '',
        'age': 4,
        'breed': 'Pamerion',
        'color': 'White'
    }

def test_valid_update_puppy(self):
    response = client.put(
        reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
        data=json.dumps(self.valid_payload),
        content_type='application/json'
    )
    self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

def test_invalid_update_puppy(self):
    response = client.put(
        reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
        data=json.dumps(self.invalid_payload),
        content_type='application/json')
    self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Run the tests.

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 405 != 400

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 405 != 204


Update the view:

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
try:
puppy = Puppy.objects.get(pk=pk)
except Puppy.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

# get details of a single puppy
if request.method == 'GET':
    serializer = PuppySerializer(puppy)
    return Response(serializer.data)

# update details of a single puppy
if request.method == 'PUT':
    serializer = PuppySerializer(puppy, data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

# delete a single puppy
elif request.method == 'DELETE':
    return Response({})

In the above snippet, similar to an insert, we serialize and validate the request data and then respond appropriately.

Run the tests again to ensure that all the tests pass.



### DELETE

To delete a single record, an ID is required:

class DeleteSinglePuppyTest(TestCase):
""" Test module for deleting an existing puppy record """

def setUp(self):
    self.casper = Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    self.muffin = Puppy.objects.create(
        name='Muffy', age=1, breed='Gradane', color='Brown')

def test_valid_delete_puppy(self):
    response = client.delete(
        reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}))
    self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

def test_invalid_delete_puppy(self):
    response = client.delete(
        reverse('get_delete_update_puppy', kwargs={'pk': 30}))
    self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

Run the tests. You should see:

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 200 != 204


Update the view:

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
try:
puppy = Puppy.objects.get(pk=pk)
except Puppy.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

# get details of a single puppy
if request.method == 'GET':
    serializer = PuppySerializer(puppy)
    return Response(serializer.data)

# update details of a single puppy
if request.method == 'PUT':
    serializer = PuppySerializer(puppy, data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

# delete a single puppy
if request.method == 'DELETE':
    puppy.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)

Run the tests again. Make sure all of them pass. Make sure to test out the UPDATE and DELETE functionality within the Browsable API as well!

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!

Follow me on [**Facebook**](https://www.facebook.com/moriohdotcom "**Facebook**") | [**Twitter**](https://twitter.com/moriohdotcom "**Twitter**")
### Learn More

☞ [Python and Django Full Stack Web Developer Bootcamp](http://learnstartup.net/p/r1-quFMgce "Python and Django Full Stack Web Developer Bootcamp")

☞ [Build a Backend REST API with Python & Django - Advanced](http://learnstartup.net/p/VyGrb6hcg "Build a Backend REST API with Python & Django - Advanced")

☞ [Python Django Dev To Deployment](http://learnstartup.net/p/9yr7V_-O8 "Python Django Dev To Deployment")

☞ [Django Core | A Reference Guide to Core Django Concepts](http://learnstartup.net/p/S1eqVBxZg "Django Core | A Reference Guide to Core Django Concepts")

☞ [Build Your First Python and Django Application ](http://dev.edupioneer.net/1935fcab7e%22%22 "Build Your First Python and Django Application ")

☞ [Flask Or Django? An In-Depth Comparison](http://on.morioh.net/108ddbf9b5 "Flask Or Django? An In-Depth Comparison")

☞ [How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 16.04](http://on.geeklearn.net/73b1fadaaf "How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 16.04")

☞ [Building a Universal Application with Nuxt.js and Django](http://on.geeklearn.net/0bda45c395 "Building a Universal Application with Nuxt.js and Django")

☞ [Django Tutorial: Building and Securing Web Applications](http://on.morioh.net/be6171a7c3 "Django Tutorial: Building and Securing Web Applications")

*Originally published on *[*https://realpython.com*](https://realpython.com "*https://realpython.com*")
,
        views.get_delete_update_puppy,
        name='get_delete_update_puppy'
    ),
    url(
        r'^api/v1/puppies/
Update *django-puppy-store/puppy_store/puppy_store/urls.py* as well:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
url(r'^', include('puppies.urls')),
url(
r'^api-auth/',
include('rest_framework.urls', namespace='rest_framework')
),
url(r'^admin/', admin.site.urls),
]

## Browsable API

With all routes now wired up with the view functions, let’s open up REST Framework’s Browsable API interface and verify whether all the URLs are working as expected.

First, fire up the development server:

(env)$ python manage.py runserver


Make sure to comment out all the attributes in ```REST_FRAMEWORK``` section of our ```settings.py``` file, to bypass login. Now visit ```<a href="http://localhost:8000/api/v1/puppies" target="_blank">http://localhost:8000/api/v1/puppies</a>```

You will see an interactive HTML layout for the API response. Similarly we can test the other URLs and verify all URLs are working perfectly fine.

Let’s start with our unit tests for each route.



## Routes
### GET ALL

Start with a test to verify the fetched records:

class GetAllPuppiesTest(TestCase):
""" Test module for GET all puppies API """

def setUp(self):
    Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    Puppy.objects.create(
        name='Muffin', age=1, breed='Gradane', color='Brown')
    Puppy.objects.create(
        name='Rambo', age=2, breed='Labrador', color='Black')
    Puppy.objects.create(
        name='Ricky', age=6, breed='Labrador', color='Brown')

def test_get_all_puppies(self):
    # get API response
    response = client.get(reverse('get_post_puppies'))
    # get data from db
    puppies = Puppy.objects.all()
    serializer = PuppySerializer(puppies, many=True)
    self.assertEqual(response.data, serializer.data)
    self.assertEqual(response.status_code, status.HTTP_200_OK)

Run the test. You should see the following error:

self.assertEqual(response.data, serializer.data)
AssertionError: {} != [OrderedDict([('name', 'Casper'), ('age',[687 chars])])]


Update the view to get the test to pass.

@api_view(['GET', 'POST'])
def get_post_puppies(request):
# get all puppies
if request.method == 'GET':
puppies = Puppy.objects.all()
serializer = PuppySerializer(puppies, many=True)
return Response(serializer.data)
# insert a new record for a puppy
elif request.method == 'POST':
return Response({})


Here, we get all the records for puppies and validate each using the ```PuppySerializer```.

Run the tests to ensure they all pass:

Ran 2 tests in 0.072s

OK

### GET Single

Fetching a single puppy involves two test cases:
1. Get valid puppy - e.g., the puppy exists
1. Get invalid puppy - e.g., the puppy does not exists

Add the tests:

class GetSinglePuppyTest(TestCase):
""" Test module for GET single puppy API """

def setUp(self):
    self.casper = Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    self.muffin = Puppy.objects.create(
        name='Muffin', age=1, breed='Gradane', color='Brown')
    self.rambo = Puppy.objects.create(
        name='Rambo', age=2, breed='Labrador', color='Black')
    self.ricky = Puppy.objects.create(
        name='Ricky', age=6, breed='Labrador', color='Brown')

def test_get_valid_single_puppy(self):
    response = client.get(
        reverse('get_delete_update_puppy', kwargs={'pk': self.rambo.pk}))
    puppy = Puppy.objects.get(pk=self.rambo.pk)
    serializer = PuppySerializer(puppy)
    self.assertEqual(response.data, serializer.data)
    self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_get_invalid_single_puppy(self):
    response = client.get(
        reverse('get_delete_update_puppy', kwargs={'pk': 30}))
    self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

Run the tests. You should see the following error:

self.assertEqual(response.data, serializer.data)
AssertionError: {} != {'name': 'Rambo', 'age': 2, 'breed': 'Labr[109 chars]26Z'}


Update the view:

@api_view(['GET', 'UPDATE', 'DELETE'])
def get_delete_update_puppy(request, pk):
try:
puppy = Puppy.objects.get(pk=pk)
except Puppy.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

# get details of a single puppy
if request.method == 'GET':
    serializer = PuppySerializer(puppy)
    return Response(serializer.data)

In the above snippet, we get the puppy using an ID. Run the tests to ensure they all pass.



### POST

Inserting a new record involves two cases as well:
1. Inserting a valid puppy
1. Inserting a invalid puppy

First, write tests for it:

class CreateNewPuppyTest(TestCase):
""" Test module for inserting a new puppy """

def setUp(self):
    self.valid_payload = {
        'name': 'Muffin',
        'age': 4,
        'breed': 'Pamerion',
        'color': 'White'
    }
    self.invalid_payload = {
        'name': '',
        'age': 4,
        'breed': 'Pamerion',
        'color': 'White'
    }

def test_create_valid_puppy(self):
    response = client.post(
        reverse('get_post_puppies'),
        data=json.dumps(self.valid_payload),
        content_type='application/json'
    )
    self.assertEqual(response.status_code, status.HTTP_201_CREATED)

def test_create_invalid_puppy(self):
    response = client.post(
        reverse('get_post_puppies'),
        data=json.dumps(self.invalid_payload),
        content_type='application/json'
    )
    self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Run the tests. You should see two failures:

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 200 != 400

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
AssertionError: 200 != 201


Again, update the view to get the tests to pass:

@api_view(['GET', 'POST'])
def get_post_puppies(request):
# get all puppies
if request.method == 'GET':
puppies = Puppy.objects.all()
serializer = PuppySerializer(puppies, many=True)
return Response(serializer.data)
# insert a new record for a puppy
if request.method == 'POST':
data = {
'name': request.data.get('name'),
'age': int(request.data.get('age')),
'breed': request.data.get('breed'),
'color': request.data.get('color')
}
serializer = PuppySerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


Here, we inserted a new record by serializing and validating the request data before inserting to the database.

Run the tests again to ensure they pass.

You can also test this out with the Browsable API. Fire up the development server again, and navigate to [http://localhost:8000/api/v1/puppies/](http://localhost:8000/api/v1/puppies/ "http://localhost:8000/api/v1/puppies/"). Then, within the POST form, submit the following as ```application/json```:

{
"name": "Muffin",
"age": 4,
"breed": "Pamerion",
"color": "White"
}


Be sure the GET ALL and Get Single work as well.



### PUT

Start with a test to update a record. Similar to adding a record, we again need to test for both valid and invalid updates:

class UpdateSinglePuppyTest(TestCase):
""" Test module for updating an existing puppy record """

def setUp(self):
    self.casper = Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    self.muffin = Puppy.objects.create(
        name='Muffy', age=1, breed='Gradane', color='Brown')
    self.valid_payload = {
        'name': 'Muffy',
        'age': 2,
        'breed': 'Labrador',
        'color': 'Black'
    }
    self.invalid_payload = {
        'name': '',
        'age': 4,
        'breed': 'Pamerion',
        'color': 'White'
    }

def test_valid_update_puppy(self):
    response = client.put(
        reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
        data=json.dumps(self.valid_payload),
        content_type='application/json'
    )
    self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

def test_invalid_update_puppy(self):
    response = client.put(
        reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
        data=json.dumps(self.invalid_payload),
        content_type='application/json')
    self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Run the tests.

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 405 != 400

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 405 != 204


Update the view:

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
try:
puppy = Puppy.objects.get(pk=pk)
except Puppy.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

# get details of a single puppy
if request.method == 'GET':
    serializer = PuppySerializer(puppy)
    return Response(serializer.data)

# update details of a single puppy
if request.method == 'PUT':
    serializer = PuppySerializer(puppy, data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

# delete a single puppy
elif request.method == 'DELETE':
    return Response({})

In the above snippet, similar to an insert, we serialize and validate the request data and then respond appropriately.

Run the tests again to ensure that all the tests pass.



### DELETE

To delete a single record, an ID is required:

class DeleteSinglePuppyTest(TestCase):
""" Test module for deleting an existing puppy record """

def setUp(self):
    self.casper = Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    self.muffin = Puppy.objects.create(
        name='Muffy', age=1, breed='Gradane', color='Brown')

def test_valid_delete_puppy(self):
    response = client.delete(
        reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}))
    self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

def test_invalid_delete_puppy(self):
    response = client.delete(
        reverse('get_delete_update_puppy', kwargs={'pk': 30}))
    self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

Run the tests. You should see:

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 200 != 204


Update the view:

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
try:
puppy = Puppy.objects.get(pk=pk)
except Puppy.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

# get details of a single puppy
if request.method == 'GET':
    serializer = PuppySerializer(puppy)
    return Response(serializer.data)

# update details of a single puppy
if request.method == 'PUT':
    serializer = PuppySerializer(puppy, data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

# delete a single puppy
if request.method == 'DELETE':
    puppy.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)

Run the tests again. Make sure all of them pass. Make sure to test out the UPDATE and DELETE functionality within the Browsable API as well!

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!

Follow me on [**Facebook**](https://www.facebook.com/moriohdotcom "**Facebook**") | [**Twitter**](https://twitter.com/moriohdotcom "**Twitter**")
### Learn More

☞ [Python and Django Full Stack Web Developer Bootcamp](http://learnstartup.net/p/r1-quFMgce "Python and Django Full Stack Web Developer Bootcamp")

☞ [Build a Backend REST API with Python & Django - Advanced](http://learnstartup.net/p/VyGrb6hcg "Build a Backend REST API with Python & Django - Advanced")

☞ [Python Django Dev To Deployment](http://learnstartup.net/p/9yr7V_-O8 "Python Django Dev To Deployment")

☞ [Django Core | A Reference Guide to Core Django Concepts](http://learnstartup.net/p/S1eqVBxZg "Django Core | A Reference Guide to Core Django Concepts")

☞ [Build Your First Python and Django Application ](http://dev.edupioneer.net/1935fcab7e%22%22 "Build Your First Python and Django Application ")

☞ [Flask Or Django? An In-Depth Comparison](http://on.morioh.net/108ddbf9b5 "Flask Or Django? An In-Depth Comparison")

☞ [How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 16.04](http://on.geeklearn.net/73b1fadaaf "How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 16.04")

☞ [Building a Universal Application with Nuxt.js and Django](http://on.geeklearn.net/0bda45c395 "Building a Universal Application with Nuxt.js and Django")

☞ [Django Tutorial: Building and Securing Web Applications](http://on.morioh.net/be6171a7c3 "Django Tutorial: Building and Securing Web Applications")

*Originally published on *[*https://realpython.com*](https://realpython.com "*https://realpython.com*")
,
        views.get_post_puppies,
        name='get_post_puppies'
    )
]

Update django-puppy-store/puppy_store/puppy_store/urls.py as well:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^', include('puppies.urls')),
    url(
        r'^api-auth/',
        include('rest_framework.urls', namespace='rest_framework')
    ),
    url(r'^admin/', admin.site.urls),
]

Browsable API

With all routes now wired up with the view functions, let’s open up REST Framework’s Browsable API interface and verify whether all the URLs are working as expected.

First, fire up the development server:

(env)$ python manage.py runserver

Make sure to comment out all the attributes in REST_FRAMEWORK section of our settings.py file, to bypass login. Now visit <a href="http://localhost:8000/api/v1/puppies" target="_blank">http://localhost:8000/api/v1/puppies</a>

You will see an interactive HTML layout for the API response. Similarly we can test the other URLs and verify all URLs are working perfectly fine.

Let’s start with our unit tests for each route.

Routes

GET ALL

Start with a test to verify the fetched records:

class GetAllPuppiesTest(TestCase):
    """ Test module for GET all puppies API """

    def setUp(self):
        Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        Puppy.objects.create(
            name='Muffin', age=1, breed='Gradane', color='Brown')
        Puppy.objects.create(
            name='Rambo', age=2, breed='Labrador', color='Black')
        Puppy.objects.create(
            name='Ricky', age=6, breed='Labrador', color='Brown')

    def test_get_all_puppies(self):
        # get API response
        response = client.get(reverse('get_post_puppies'))
        # get data from db
        puppies = Puppy.objects.all()
        serializer = PuppySerializer(puppies, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

Run the test. You should see the following error:

self.assertEqual(response.data, serializer.data)
AssertionError: {} != [OrderedDict([('name', 'Casper'), ('age',[687 chars])])]

Update the view to get the test to pass.

@api_view(['GET', 'POST'])
def get_post_puppies(request):
    # get all puppies
    if request.method == 'GET':
        puppies = Puppy.objects.all()
        serializer = PuppySerializer(puppies, many=True)
        return Response(serializer.data)
    # insert a new record for a puppy
    elif request.method == 'POST':
        return Response({})

Here, we get all the records for puppies and validate each using the PuppySerializer.

Run the tests to ensure they all pass:

Ran 2 tests in 0.072s

OK

GET Single

Fetching a single puppy involves two test cases:

  1. Get valid puppy - e.g., the puppy exists
  2. Get invalid puppy - e.g., the puppy does not exists

Add the tests:

class GetSinglePuppyTest(TestCase):
    """ Test module for GET single puppy API """

    def setUp(self):
        self.casper = Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        self.muffin = Puppy.objects.create(
            name='Muffin', age=1, breed='Gradane', color='Brown')
        self.rambo = Puppy.objects.create(
            name='Rambo', age=2, breed='Labrador', color='Black')
        self.ricky = Puppy.objects.create(
            name='Ricky', age=6, breed='Labrador', color='Brown')

    def test_get_valid_single_puppy(self):
        response = client.get(
            reverse('get_delete_update_puppy', kwargs={'pk': self.rambo.pk}))
        puppy = Puppy.objects.get(pk=self.rambo.pk)
        serializer = PuppySerializer(puppy)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_get_invalid_single_puppy(self):
        response = client.get(
            reverse('get_delete_update_puppy', kwargs={'pk': 30}))
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

Run the tests. You should see the following error:

self.assertEqual(response.data, serializer.data)
AssertionError: {} != {'name': 'Rambo', 'age': 2, 'breed': 'Labr[109 chars]26Z'}

Update the view:

@api_view(['GET', 'UPDATE', 'DELETE'])
def get_delete_update_puppy(request, pk):
    try:
        puppy = Puppy.objects.get(pk=pk)
    except Puppy.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    # get details of a single puppy
    if request.method == 'GET':
        serializer = PuppySerializer(puppy)
        return Response(serializer.data)

In the above snippet, we get the puppy using an ID. Run the tests to ensure they all pass.

POST

Inserting a new record involves two cases as well:

  1. Inserting a valid puppy
  2. Inserting a invalid puppy

First, write tests for it:

class CreateNewPuppyTest(TestCase):
    """ Test module for inserting a new puppy """

    def setUp(self):
        self.valid_payload = {
            'name': 'Muffin',
            'age': 4,
            'breed': 'Pamerion',
            'color': 'White'
        }
        self.invalid_payload = {
            'name': '',
            'age': 4,
            'breed': 'Pamerion',
            'color': 'White'
        }

    def test_create_valid_puppy(self):
        response = client.post(
            reverse('get_post_puppies'),
            data=json.dumps(self.valid_payload),
            content_type='application/json'
        )
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    def test_create_invalid_puppy(self):
        response = client.post(
            reverse('get_post_puppies'),
            data=json.dumps(self.invalid_payload),
            content_type='application/json'
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Run the tests. You should see two failures:

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 200 != 400

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
AssertionError: 200 != 201

Again, update the view to get the tests to pass:

@api_view(['GET', 'POST'])
def get_post_puppies(request):
    # get all puppies
    if request.method == 'GET':
        puppies = Puppy.objects.all()
        serializer = PuppySerializer(puppies, many=True)
        return Response(serializer.data)
    # insert a new record for a puppy
    if request.method == 'POST':
        data = {
            'name': request.data.get('name'),
            'age': int(request.data.get('age')),
            'breed': request.data.get('breed'),
            'color': request.data.get('color')
        }
        serializer = PuppySerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Here, we inserted a new record by serializing and validating the request data before inserting to the database.

Run the tests again to ensure they pass.

You can also test this out with the Browsable API. Fire up the development server again, and navigate to http://localhost:8000/api/v1/puppies/. Then, within the POST form, submit the following as application/json:

{
    "name": "Muffin",
    "age": 4,
    "breed": "Pamerion",
    "color": "White"
}

Be sure the GET ALL and Get Single work as well.

PUT

Start with a test to update a record. Similar to adding a record, we again need to test for both valid and invalid updates:

class UpdateSinglePuppyTest(TestCase):
    """ Test module for updating an existing puppy record """

    def setUp(self):
        self.casper = Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        self.muffin = Puppy.objects.create(
            name='Muffy', age=1, breed='Gradane', color='Brown')
        self.valid_payload = {
            'name': 'Muffy',
            'age': 2,
            'breed': 'Labrador',
            'color': 'Black'
        }
        self.invalid_payload = {
            'name': '',
            'age': 4,
            'breed': 'Pamerion',
            'color': 'White'
        }

    def test_valid_update_puppy(self):
        response = client.put(
            reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
            data=json.dumps(self.valid_payload),
            content_type='application/json'
        )
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

    def test_invalid_update_puppy(self):
        response = client.put(
            reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
            data=json.dumps(self.invalid_payload),
            content_type='application/json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Run the tests.

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 405 != 400

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 405 != 204

Update the view:

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
    try:
        puppy = Puppy.objects.get(pk=pk)
    except Puppy.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    # get details of a single puppy
    if request.method == 'GET':
        serializer = PuppySerializer(puppy)
        return Response(serializer.data)

    # update details of a single puppy
    if request.method == 'PUT':
        serializer = PuppySerializer(puppy, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    # delete a single puppy
    elif request.method == 'DELETE':
        return Response({})

In the above snippet, similar to an insert, we serialize and validate the request data and then respond appropriately.

Run the tests again to ensure that all the tests pass.

DELETE

To delete a single record, an ID is required:

class DeleteSinglePuppyTest(TestCase):
    """ Test module for deleting an existing puppy record """

    def setUp(self):
        self.casper = Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        self.muffin = Puppy.objects.create(
            name='Muffy', age=1, breed='Gradane', color='Brown')

    def test_valid_delete_puppy(self):
        response = client.delete(
            reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}))
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

    def test_invalid_delete_puppy(self):
        response = client.delete(
            reverse('get_delete_update_puppy', kwargs={'pk': 30}))
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

Run the tests. You should see:

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 200 != 204

Update the view:

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
    try:
        puppy = Puppy.objects.get(pk=pk)
    except Puppy.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    # get details of a single puppy
    if request.method == 'GET':
        serializer = PuppySerializer(puppy)
        return Response(serializer.data)

    # update details of a single puppy
    if request.method == 'PUT':
        serializer = PuppySerializer(puppy, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    # delete a single puppy
    if request.method == 'DELETE':
        puppy.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Run the tests again. Make sure all of them pass. Make sure to test out the UPDATE and DELETE functionality within the Browsable API as well!