Skip to main content

Controllers

Learn

A controller is the first place on HTTP request goes once it reaches your application. it is, in essence, translator between the HTTP request and your code. In spring Boot, we use the @RestController annotation to tell spring that our class is controller.

Controller contain endpoints, which describe the URIs the client application can access. we use annotation for these as well, which we will discuss later on.

What is JUnit?​

Junit is a common java unit testing framework. It comes bundled with Intellij and makes automating test cse simple.

What is REST Assured?​

Rest Assured is a HTTP functional testing framework. It works by generating real HTTP request to an endpoint and allowing you to run assertions against the responses.


note

Any code below with a package or import that includes bootcamp as part of its path should replace bootcamp with your project name.

Do​

1. Create New Working Branch​

It is good practice when making changes to a git codebase to create nd work on new branch so that your changes do not impact production code should they contain an error. A new branch should be made off your primary branch for every new feature/modification.

First, try to get the latest code to avoid merge conflicts and resolve if any.

git pull

Create a new local branch and switch to it executive the following command int the Intellij terminal:

git checkout -b <branch>

where <branch> is the name of new branch.

The checkout command will move us to this new branch while the -b flag will create this branch for us should it not yet exist. We are now working on a new branch.

2. Write Student object​

Write the Student POJO (plain old Java object) with a default constructor, parameterized constructor, and getters.

In the src/main/java/com.geteche.students directory, create a new package called model.

In the newly created package, create a new java class called Student.java.

Include private variables id and name, use Intellij to generate getters for those fields, and add 2 constructors in the class.

Student.java
public class Student {

private Integer id;
private String name;

public Student() {}

public Student(Integer id, String name) {
this.id = id;
this.name = name;
}
...
}
note

For those who are more familiar with java, the reason why we use the Integer class instead of the int primitive here will become clear later on.

3. Write StudentController​

Write the StudentController with one GET endpoint that will retrieve a single Student.

In the src/main/java/com.geteche.students directory, create a new package called controller.

In the newly created package, create a new Java class called StudentController.java.

Based on the annotation and method header in the code snippet below, implement the getStudentById method to return single Student object.

@GetMapping(
value = "/v1/students/{studentId}",
products = MediaType.APPLICATION_JSON_VALUE)
public Student getStudentById(@PathVariable("studentId") Integer id)

We use the @GetMapping annotation to tell the controller that we want requests to the given HTTP GET endpoint to call the handler methods we describe. we call this a mapping.

There are two parameters in this annotation:

  1. value, which tells the controller what the URI should look like; and
  2. produces, which tells the controller that the HTTP response it sends back to the client should be encoded as JSON (JavaScript Object Notation).

The String we pass to value is taced on to the end of the URI (hence, endpoint). Notice the braces ({}) in the string. These braces denote a variable in the URI. Each of our Student objects will be given a unique ID, and if we use this ID in our HTTP request, the controller will parse it and assign its value to a parameter in the method call. we do this with the @PathVariable annotation you can see in the method header.

For example, if you wanted to retrieve the Student with ID 1, your request URL would look like: http://localhost:8080/api/v1/students/1. The application would then use the value 1 in its (hidden) call to getStudentById().

4. Configure base URL​

src/main/resources/application.properties
server.servlet.context-path=/api

5. Write tests​

Write a JUnit unit test and a REST Assured integration test for the StudentController.

Unit test​

In the src/test/java/com.geteche.students directory, create a new package called controller.

In the newly created package, create a new Java class called StudentControllerTest.java.

Use the @SpringBootTest annotation to tell Spring Boot to start a Spring application context for the @SpringBootApplication, include a dependency on StudentController, and use the @Test annotation to signify the method is a unit test.

@SpringBootTest
public class StudentControllerTest {

@Autowired
private StudentController StudentController;

@Test
public void getStudentByIdTest() {
...
}
...
}

To execute the unit tests, run the following command in the Intellij terminal:

.\gradlew test

6. Debug​

It is important to know how to debug, especially when trying to fix your tests that might be broken. In unit tests, it is as easy as setting breakpoints and start debugging. But in integration tests, you need to create a remote JVM debug configuration and attach debugger to process, because your application is running in Docker container, which is seen as a remote application.

7. Commit and push changes​

Now that your changes have been made to the codebase, it is time to stage all of your changes for committing by executing:

git add . 
note

Using . will add of your changed files to the commit. Sometimes, you may not want to add everything. In this case, you can instead provide the names of specific files or directories you wish to commit using:

git add <file>

Alternatively, you can review all of the changes you made before adding them with the command:

git add --patch

Now, commit your changes by executing:

git commit -m "<message>"

where <message> is your commit message describing your changes.

tip

It's common practice to write commit messages in the present tense rather than in the past tense (e.g. "Fix broken link" instead of "Fixed broken link"). Additionally, keep your commit messages as concise as possible. This blog post by Chris Beams provides an in-depth discussion on the importance of good commit messages, as well as guidelines for writing good messages.

Finally, push your changes to a new remote branch in your Github repository by executing:

git push --set-upstream origin <branch>

where <branch> is your new branch name from before.

note

Instead of --set-upstream, you can also use the shorthand -u flag. It will achieve the same result. And this is usually needed the first time only. After that, you don't need this flag.

8. Open pull request​

In GitHub, open a pull request and select one or more individuals to review your changes. Provide additional comments as necessary.

As soon as you create the pull request, a new build in Jenkins will start. This build will build and test your new code and ensure that is passes quality gateways. If the build fails, you may need to go back and make changes to your code.

9. Merge branch into master​

After your code has been reviewed and the Jenkins build has completed successfully, you will be able to merge your code into master branch. After doing so, keeping in line with the trunk-based branching strategy, you can safely delete the branch.

After merging into master another Jenkins build will start to deploy your application to nonprod.

10. Prepare for further development​

Now that the changes have been merged with the remote master branch, we need to update our local copy. To do so, first switch over to your local copy of master by executing the following command in the IntelliJ terminal:

git checkout master 

Next, retrieve your changes by executing:

git pull 

You can also safely delete your old branch by executing:

git branch -d <branch>

where <branch> is the name of your branch from before.

note

Sometimes, Git will warn your that there are uncommitted changes on the branch your are truing to delete or that your local branch has not been fully merged. If, however, you know that all the correct changes have been committed, pushed, and merged successfully, you can use the -D flag (instead of -d) to force the branch to be deleted.