Top 5 Java Test Frameworks for Automation in 2019

Top 5 Java Test Frameworks for Automation in 2019

Check out this post to learn more about the top Java test frameworks for automation testing.

For decades, Java has been the most preferred programming language for developing the server side layer of an application. Although JUnit has been there with the developers for helping them in automated unit testing, with time and the evolution of testing, when automation testing is currently on the rise, many open-source frameworks have been developed that are based on Java and varying a lot from JUnit in terms of validation and business logic. Here, I will be talking about the top five Java test frameworks of 2019 for performing test automation with Selenium WebDriver and Java. I will also highlight what is unique about these top Java test frameworks.


JUnit

Kent Beck and Erich Gamma developer JUnit, which is an instance of xUnit. The main purpose behind it was to enable Java developers for scripting and executing repeatable test cases. It is usually used to test small chunks of code. You can also perform automation testing of a website by integrating JUnit with Selenium WebDriver for Java test automation. Whenever any new piece of code is added, you will be required to re-execute the entire test cases and make sure that there is no breakage.


What Are the Prerequisites?

The framework is highly compatible with Selenium WebDriver for Java, with the reason being that JUnit is a framework based on Java. As some prerequisites, you will need to:

  • Have the latest version of JDK installed in your workstation.
  • Download the latest version of JUnit and set up the environment.
  • Have a good knowledge of application development in an object-oriented programming language, especially Java.

Advantages and Disadvantages of Using JUnit?

There are several advantages to JUnit.

  • Developers who work in an environment that is test driven find it highly beneficial since they are forced to read the code and find if there is an anomaly.
  • Bugs are detected early, resulting in a more reliable code.
  • Development of a more readable and error-free code acts as a confidence booster.
  • With the latest version of JUnit (version 4), exceptions are identified easily. You can even execute test cases written in the old versions of JUnit.
  • You can also use it with Java 5 or versions above.
  • The framework is unable to execute dependency tests. That is where we need TestNG.

The only disadvantage of JUnit is:

  • The framework is unable to execute dependency tests. That is where we need TestNG.

Is JUnit the Top Java Test Framework for You?

Both JUnit, as well as TestNG, perform the same job. And their features are almost the same, except dependency tests are not performed using JUnit and the implementation procedure of parameterized test is different in both the frameworks. Also, since JUnit is being used for a long time, there is better community support and it has been defined as the standard for unit testing using Selenium WebDriver for Java-based applications. Although TestNG has few users, the community is pretty huge and growing every day. So, we can conclude that the selection between TestNG or JUnit for a Java test framework depends entirely upon the application’s nature and requirement.

If you wish to get started with JUnit as your Java test framework, here is a step-by-step process of performing automated testing with JUnit and Selenium for browser compatibility.


JBehave

We all know about Behavior Driven Development or BDD. It is the type of testing that describes acceptance testing in a transparent manner to the business users. JBehave is another Java test framework used for BDD testing, mostly used with Selenium WebDriver for Java. The primary objective behind the introduction of JBehave is to make it possible for newcomers to easily understand and get familiar with BDD. It is kind of a design philosophy that makes the testing phase of an application more based on its behavior.


What Are the Prerequisites?

The ideal way to use JBehave is by integrating with Eclipse. For that, apart from JDK1.7 and above and any Eclipse IDE above Indigo, you will need several jar files like:

  • Junit-4.1.0.jar
  • Jbehave-core-3.8.jar
  • Commons-lang-2.4.jar
  • Paranamer-2.5.jar
  • Freemarker-2.3.9.jar
  • Org.apacje.commons.io.jar
  • Org.apache.commons.collections.jar
  • Plexus-utils-1.1.jar

Advantages and Disadvantages of JBehave

Like all other BDD testing frameworks, JBehave is also advantageous in many ways.

  • Serves the most important purpose of behavior driven development by enabling better coordination between different development teams who are working on different projects with similar specs.
  • Project managers and stakeholders get a better clarity regarding the Dev team and QA team’s output since the specs have a similar format.
  • Products have better specifications since JBehave features reasoning and thinking in details.
  • JBehave uses a semi-formal language and it also has a domain vocabulary which helps to maintain a consistent behavior in the team structure.

Well, there is only one disadvantage of JBehave just like any other BDD testing tool.

  • The success of a BDD testing tool depends mostly on the communication carried out between different members involved in a project, the stakeholders, the developers, the testers as well as the management of the organization. Lack of communication may lead to unanswered questions and doubts which may ultimately lead to a buggy application or an application not satisfying the business requirement, resulting in all the parties blaming each other.

Is JBehave The Top Java Test Framework For You?

JBehave works the same way as Serenity. However, if you are aiming to make your automated acceptance tests more efficient, it is ideal to integrate Serenity along with JBehave for utilizing a better testing experience. This is because Serenity’s core concept is based on BDD development and it also enables the users to write a powerful and rich test report. We’ll discuss more on it in the following section.


Serenity

Serenity is an open-source library meant for behavior-driven testing. The framework helps you to write acceptance criteria, which are well-structured and more maintained. It extends the functionality of JUnit and WebDriver. Initially, it was known as Thucydides. The reason behind its name change was nothing but the difficulty in pronunciation. Still, the old name can be used while referring.


What Are the Prerequisites for Installing Serenity?

Since the framework is Java-based, you will obviously need knowledge and experience in Java or any other object-oriented programming language. Apart from that, in your workstation, you will need:

  • JDK 5 or higher version.
  • Maven 3.0 or any version above.
  • An IDE tool – Eclipse is usually preferred by all developers since a combination of Maven and Eclipse is easier to work with.

Advantages and Disadvantages of Serenity

Let’s take a look at the advantages of Serenity brought into the world of behavior driven development.

  • The framework helps the tester in creating test cases for REST services.
  • Users do not have to spend a lot of time in maintaining or building their own automation framework.
  • It keeps a balance, rather a boundary between the definition and understanding of a test case and its way of implementation.
  • It can be integrated with lots of automation frameworks like Selenium, JBehave, advanced Java-based development frameworks like Spring and even with continuous integration tools like JIRA.

The only disadvantage of Serenity is just like JBehave. Constant communication between the project participants is required in order to utilize its complete feature of supporting a behavior driven development.


Is Serenity the Best Java Test Framework for You?

The tool is mainly used for reporting acceptance criteria. However, the reports written using Serenity are much informative and rich than JBehave or any other BDD framework. It also helps developers to write automation test case scenarios of better quality and also provides support for RestAssured and Selenium WebDriver, making it faster and simpler for the tester to write automation based acceptance test criteria.


TestNG

Cedric Beust created TestNG, which is an open-source, Java-based automation testing framework inspired by JUnit. However, it is much more powerful. The NG means Next Generation in TestNG. The framework is designed to perform in a better manner during integration. The framework helps developers with features like sequencing, grouping, and parameterizing, thereby making the new test cases more flexible and eliminating the drawbacks of frameworks used earlier.


Prerequisites for Installing TestNG

For installing TestNG, you will need:

  • The latest version of JDK installed in your system.
  • The latest version of Eclipse
  • Knowledge and hands-on experience of Java or any other object-oriented programming language

Advantages and Disadvantages of TestNG

Apart from overcoming the drawbacks of older frameworks, there are several advantages of TestNG.

  • The framework enables you to run parallel tests on multiple code snippets.
  • During the test case executing, you can generate an HTML report.
  • Test cases can be grouped and arranged according to priorities. Executing the tests are much simpler just by asking the framework to run database tests or front-end tests or whatever you want.
  • You can parameterize the data and use annotations to easily set the priorities.

The only disadvantage is:

  • It depends on your requirement. You may not want to use TestNG if your project does not require test case prioritization. Besides, setting up TestNG requires a bit of time.

Is TestNG the Best Java Test Framework for You?

Although JUnit performs all the same functionalities as TestNG, it has certainly has its limitations. JUnit is perfect when testing is done in isolation. But when there are dependencies; you will not have any control over which test is executed first. TestNG helps you by allowing you to run the test cases in any order you want. Besides, it also helps in parameterized testing. Although this feature is already present in JUnit 4.5, TestNG is much more effective. If your project is complicated and you may need more than a hundred test cases, it is ideal to take some time and set up TestNG instead of depending on JUnit.

If you are new to TestNG, then here is a complete guide for running your first TestNG automation script for performing automated cross-browser testing using Selenium WebDriver with Java.


Selenide

Powered by Selenium, Selenide is a popular tool for writing stable, precise, and expressive UI test cases for a Java application. Testing of modern web technologies like Ajax has certain complications like a timeout. WebDriver is the popular tool for UI tests, yet it lacked the feature to deal with a timeout. Selenide deals with all these problems in a simple manner. Also, it’s easier to grab and learn. You don’t need any requirement for Google and search for tutorials. All you need to do is focus on the business logic, and in just a few simple lines of codes, your job will be done.


What Are the Prerequisites for Using Selenide?

Installation of Selenide is as simple as it can be. If you are using Maven, you will need to add the following lines in your pom.xml file.

<pre class="ql-syntax" spellcheck="false"><dependency> <groupId>com.codeborne</groupId> <artifactId>selenide</artifactId> <version>5.1.0</version> <scope>test</scope> </dependency> </pre>

If you are using Ivy in the ivy.xml file, you will need to add:

<pre class="ql-syntax" spellcheck="false"><ivy-module> <dependencies> <dependency org="com.codeborne" name="selenide" rev="5.1.0"/> </dependencies> </ivy-module> </pre>

And just like that, the framework is ready for you to start testing.


Advantages and Disadvantages of Selenide?

In the front-end layer of an application, where Java is used on the server side, the most common problem faced by a tester is timeouts. The test cases which you wrote may work fine at present, but after a few days, maybe some Ajax request will take up some more time than it does now or some JavaScript will run slower. Also, your system may run another process at the same time, resulting in the failure of your test case. What’s more unfortunate, you may spend days finding out the source of those problems. Selenide helps you by:

  • Concise writing procedure of test cases that eliminates the timeout problems.
  • Supports testing of applications developed using AngularJS
  • Have reduced most of the commands which were used by traditional Selenium tools.

Until now, we have not found any notable disadvantage of using Selenide. Let us know if you have found any limitations that may impact your work.


Is Selenide the Best Java Test Framework for You?

For UI testing, there is no better Java-based framework apart from Selenium WebDriver, the parent framework of Selenide. Obviously, WebDriver cannot solve problems caused by Ajax Timeout or slow running JavaScript or any Dynamic Content that takes time to load. To overcome problems, we previously used “ wait_until” or “ sleep” methods in our test cases. With Selenide, we don’t need to think about those problems anymore. Just focus on the business logic and the test cases will work fine in serving their purpose.

If you plan to opt Selenide as your Java test framework for automation testing, then here is a guide to help you run Selenium automation tests using Selenide, IntelliJ, and Maven.

In the current Agile age, developers are also required to get involved in testing. Knowledge and knowing the importance of testing is what separates professional Java developers from amateurs. It is not necessary that you should be an expert, but at least you should have the basic knowledge to write test cases that can automatically validate your code. In this article, we have gone through five tools which are preferred by developers worldwide for UI testing, unit testing, and BDD testing of an application. Based on the comparison, we can conclude that if you are sincere in your job and want to become an expert developer as well as a tester, the tools mentioned above are something on which you will find useful in your daily assignments.


Thanks for reading

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

Follow us on Facebook | Twitter

Further reading

Java Programming Masterclass for Software Developers

Selenium WebDriver with Java -Basics to Advanced+Frameworks

Java In-Depth: Become a Complete Java Engineer!

Best Java Microservices Interview Questions In 2019

Introduction to Java String Interview Questions and Answers

.NET or Java for Web Development

Built a simple word game in Java using JavaFX

Simplification and Automation in Java: Yesterday, Today and Tomorrow

Originally published by Arnab Roy at https://dzone.com

14 Tips for Writing Spring MVC Controllers

14 Tips for Writing Spring MVC Controllers

In this article, I'm going to share with you some of the fundamental techniques and best practices for writing a controller class with the Spring MVC framework. Typically, in Spring MVC, we write a controller class to handle requests coming from the client.

Then, the controller invokes a business class to process business-related tasks, and then redirects the client to a logical view name, which is resolved by Spring’s dispatcher servlet in order to render results or output. That completes a round trip of a typical request-response cycle.

Here are summary of the tips you will learn throughout this article:

  1. Using the @Controller stereotype
  2. Implementing the Controller Interface
  3. Extending the AbstractController Class
  4. Specifying URL Mapping for Handler Method
  5. Specifying HTTP Request Methods for Handler Method
  6. Mapping Request Parameters to Handler Method
  7. Returning Model And View
  8. Putting Objects into the Model
  9. Redirection in Handler Method
  10. Handling Form Submission and Form Validation
  11. Handling File Upload
  12. Autowiring Business Classes in the Controller
  13. Accessing HttpServletRequest and HttpServletResponse
  14. Following the Single Responsibility Principle

1. Using the @Controller Stereotype

This is the simplest way to create a controller class that can handle one or multiple requests. Just by annotating a class with the @Controllerstereotype, for example:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {

    @RequestMapping("/")
    public String visitHome() {

        // do something before returning view name

        return "home";
    }
}

As you can see, the visitHome() method handles requests coming to the application’s context path (/) by redirecting to the view named home.

NOTE: the **@Controller **stereotype can only be used when annotation-driven is enabled in Spring’s configuration file:

<annotation-driven />

When annotation-driven is enabled, the Spring container automatically scans for classes under the package specified in the following statement:

<context:component-scan base-package="net.codejava.spring" />

The classes annotated by the @Controllerannotation are configured as controllers. This is most preferable because of its simplicity: There's no need to declare beans for controllers in the configuration file.

NOTE: By using the @Controllerannotation, you can have a multi-actions controller class that is able to serve multiple different requests. For example:

@Controller
public class MultiActionController {

    @RequestMapping("/listUsers")
    public ModelAndView listUsers() {

    }

    @RequestMapping("/saveUser")
    public ModelAndView saveUser(User user) {

    }

    @RequestMapping("/deleteUser")
    public ModelAndView deleteUser(User user) {

    }
}

As you can see in the above controller class, there are three handler methods that process three different requests /listUsers, /saveUser, and /deleteUser, respectively.

2. Implementing the Controller Interface

Another (and maybe classic) way of creating a controller in Spring MVC is having a class implement the Controller interface. For example:

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class MainController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        System.out.println("Welcome main");
        return new ModelAndView("main");
    }
}

The implementing class must override the handleRequest() method, which will be invoked by the Spring dispatcher servlet when a matching request comes in. The request URL pattern handled by this controller is defined in the Spring’s context configuration file as follows:

<bean name="/main" class="net.codejava.spring.MainController" />

However, a drawback of this approach is that the controller class cannot handle multiple request URLs.

3. Extending the AbstractController Class

If you want to easily control the supported HTTP methods, session, and content caching. having your controller class extended the AbstractController class is ideal. Consider the following example:

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

public class BigController extends AbstractController {

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        System.out.println("You're big!");
        return new ModelAndView("big");
    }
}

This creates a single-action controller with configurations regarding the supported methods, session, and caching, which can then be specified in the bean declaration of the controller. For example:

<bean name="/big" class="net.codejava.spring.BigController">
    <property name="supportedMethods" value="POST"/>
</bean>

This configuration indicates that only the POST method is supported by this controller’s hander method. For other configuration (session, caching), see AbstractController.

Spring MVC also offers several controller classes designed for specific purposes, including:

4. Specifying URL Mapping for the Handler Method

This is the mandatory task you must do when coding a controller class, which is designed for handling one or more specific requests. Spring MVC provides the @RequestMapping annotation, which is used for specifying URL mapping. For example:

@RequestMapping("/login")

That maps the URL pattern /login to be handled by the annotated method or class. When this annotation is used at the class level, the class becomes a single-action controller. For example:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/hello")
public class SingleActionController {

    @RequestMapping(method = RequestMethod.GET)
    public String sayHello() {
        return "hello";
    }
}

When the @RequestMapping<span id="_tmp_pre_18"> </span>annotation is used at the method level, you can have a multi-action controller. For example:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class UserController {

    @RequestMapping("/listUsers")
    public String listUsers() {
        return "ListUsers";
    }

    @RequestMapping("/saveUser")
    public String saveUser() {
        return "EditUser";
    }

    @RequestMapping("/deleteUser")
    public String deleteUser() {
        return "DeleteUser";
    }
}

The @RequestMapping annotation can be also used for specifying multiple URL patterns to be handled by a single method. For example:

@RequestMapping({"/hello", "/hi", "/greetings"})

In addition, this annotation has other properties that may be useful in some cases, e.g. the method property which is covered in the next section.

5. Specifying HTTP Request Methods for the Handler Method

You can specify which HTTP method (GET, POST, PUT, …) is supported by a handler method using the method property of the @RequestMapping annotation. Here’s an example:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class LoginController {

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String viewLogin() {
        return "LoginForm";
    }

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String doLogin() {
        return "Home";
    }
}

As you can see, this controller has two methods that handle the same URL pattern /login, but the former is for the GET method and the latter is for the POST method.

For more information about using the @RequestMapping annotation, see @RequestMapping annotation.

6. Mapping Request Parameters to Handler Method

One of the cool features of Spring MVC is that you can retrieve request parameters as regular parameters of the handler method by using the @RequestParam annotation. This is a good way to decouple the controller from the HttpServletRequest interface of the Servlet API.

Let’s look at various examples. Consider the following method:

@RequestMapping(value = "/login", method = RequestMethod.POST)
public String doLogin(@RequestParam String username,
                      @RequestParam String password) {

}

Spring binds the method parameters username and password to the HTTP request parameters with the same names. That means you can invoke a URL as follows (if the request method is GET):

http://localhost:8080/spring/login?username=scott&password=tiger

Type conversion is also done automatically. For example, if you declare a parameter of type integer as follows:

@RequestParam int securityNumber

Then, Spring will automatically convert value of the request parameter (String) to the specified type (integer) in the handler method.

In case the parameter name is different than the variable name, you can specify the actual name of the parameter as follows:

@RequestParam("SSN") int securityNumber

The @RequestParam annotation also has two additional attributes, which might be useful in some cases. Therequiredattribute specifies whether the parameter is mandatory or not. For example:

@RequestParam(required = false) String country

That means the parameter country is optional; hence, it can be missing from the request. In the above example, the variable country will be null if there is no such parameter present in the request.

Another attribute is defaultValue, which can be used as a fallback value when the request parameter is empty. For example:

@RequestParam(defaultValue = "18") int age

Spring also allows us to access all parameters as a Map object if the method parameter is of type Map<String, String>. For example:

doLogin(@RequestParam Map<String, String> params)

Then the map params contains all request parameters in the form of key-value pairs. For more information about using the @RequestParam annotation, see @RequestParam annotation.

7. Returning Model and View

After business logic is processed, a handler method should return a view, which is then resolved by the Spring’s dispatcher servlet. Spring allows us to return either a String or a ModelAndView object from the handler method. In the following example, the handler method returns a String and represents a view named LoginForm:

@RequestMapping(value = "/login", method = RequestMethod.GET)
public String viewLogin() {
    return "LoginForm";
}

That’s the simplest way of returning a view name. But if you want to send additional data to the view, you must return a ModelAndView object. Consider the following handler method:

@RequestMapping("/listUsers")
public ModelAndView listUsers() {

    List<User> listUser = new ArrayList<>();
    // get user list from DAO...

    ModelAndView modelView = new ModelAndView("UserList");
    modelView.addObject("listUser", listUser);

    return modelView;
}

As you can see, this handler method returns a ModelAndView object that holds the view name UserList<span id="_tmp_pre_47"> </span>and a collection of User objects, which can be used in the view.

Spring is also very flexible, as you can declare the ModelAndView object as a parameter of the handler method instead of creating a new one. Thus, the above example can be re-written as follows:

@RequestMapping("/listUsers")
public ModelAndView listUsers(ModelAndView modelView) {

    List<User> listUser = new ArrayList<>();
    // get user list from DAO...

    modelView.setViewName("UserList");
    modelView.addObject("listUser", listUser);

    return modelView;
}

You can learn more about the ModelAndView class by visiting: ModelAndView class.

8. Putting Objects Into the Model

In an application that follows the MVC architecture, the controller (C) should pass data into the model (M), which is then used in the view (V). As we see in the previous example, the addObject() method of the ModelAndView class is for putting an object to the model, in form of name-value pair:

modelView.addObject("listUser", listUser);
modelView.addObject("siteName", new String("CodeJava.net"));
modelView.addObject("users", 1200000);

Again, Spring is very flexible. You can declare a parameter of type Map in the handler method; Spring uses this map to store objects for the model. Let’s see another example:

@RequestMapping(method = RequestMethod.GET)
public String viewStats(Map<String, Object> model) {
    model.put("siteName", "CodeJava.net");
    model.put("pageviews", 320000);

    return "Stats";
}

This is even simpler than using the ModelAndView object. Depending on your taste, you can use either Map or ModelAndView object. Thanks for the flexibility of Spring.

9. Redirection in Handler Method

In case you want to redirect the user to another URL if a condition is met, just append redirect:/ before the URL. The following code snippet gives an example:

// check login status....

if (!isLogin) {
    return new ModelAndView("redirect:/login");
}

// return a list of Users

In the above code, the user will be redirected to the /login URL if it is not logged in.

10. Handling Form Submission and Form Validation

Spring makes it easy to handle form submission by providing the @ModelAttribute annotation for binding form fields to a form backing object, and the BindingResult interface for validating form fields. The following code snippet shows a typical handler method that is responsible for handling and validating form data:

@Controller
public class RegistrationController {

    @RequestMapping(value = "/doRegister", method = RequestMethod.POST)
    public String doRegister(
        @ModelAttribute("userForm") User user, BindingResult bindingResult) {

        if (bindingResult.hasErrors()) {
            // form validation error

        } else {
            // form input is OK
        }

        // process registration...

        return "Success";
    }
}

Learn more about the @ModelAttribute annotation and the BindingResultinterface from Spring’s official documentation:

11. Handling File Upload

Spring also makes it easy to handle file upload within a handler method, by automatically binding upload data to an array of CommonsMultipartFile objects. Spring uses Apache Commons FileUpload as the underlying multipart resolver.

The following code snippet shows how easy it is to get files uploaded from the client:

@RequestMapping(value = "/uploadFiles", method = RequestMethod.POST)
public String handleFileUpload(
        @RequestParam CommonsMultipartFile[] fileUpload) throws Exception {

    for (CommonsMultipartFile aFile : fileUpload){

        // stores the uploaded file
        aFile.transferTo(new File(aFile.getOriginalFilename()));

    }

    return "Success";
}

You can learn the complete solution for handling file upload with Spring MVC by following this Spring MVC File Upload Tutorial.

12. Autowiring Business Classes in the Controller

A controller should delegate the processing of business logic to relevant business classes. For this purpose, you can use the @Autowired annotation to let Spring automatically inject the actual implementation of a business class to the controller. Consider the following code snippet of a controller class:

@Controller
public class UserController {

    @Autowired
    private UserDAO userDAO;

    public String listUser() {
        // handler method to list all users
        userDAO.list();
    }

    public String saveUser(User user) {
        // handler method to save/update a user
        userDAO.save(user);
    }

    public String deleteUser(User user) {
        // handler method to delete a user
        userDAO.delete(user);
    }

    public String getUser(int userId) {
        // handler method to get a user
        userDAO.get(userId);
    }
}

Here, all business logic related to User management is provided by an implementation of the UserDAO interface. For example:

interface UserDAO {

    List<User> list();

    void save(User user);

    void checkLogin(User user);
}

By using the @Autowired annotation, the handler methods can delegate tasks to the business class, as we can see in the above example:

List<User> listUser = userDAO.list();

For more information about the @Autowired annotation, see Annotation Type Autowired.

13. Accessing HttpServletRequest and HttpServletResponse

In some cases, you need to directly access the HttpServletRequest or HttpServletResponse objects within a handler method. By the flexibility of Spring, just add a relevant parameter to the handler method. For example:

@RequestMapping("/download")
public String doDownloadFile(
        HttpServletRequest request, HttpServletResponse response) {

    // access the request

    // access the response

    return "DownloadPage";
}

Spring detects and automatically injects the HttpServletRequest and HttpServletResponse objects into the method. Then, you can access the request and response such as getting InputStream, OutputStream, or returning a specific HTTP code.

14. Following the Single Responsibility Principle

Finally, there are two good practices you should follow when designing and coding controllers in Spring MVC:

  • A controller class should not execute business logic. Instead, it should delegate business processing to relevant business classes. This keeps the controller focusing on its designed responsibility is to control workflows of the application. For example:
@Controller
public class UserController {

    @Autowired
    private UserDAO userDAO;

    public String listUser() {
        // handler method to list all users
        userDAO.list();
    }

    public String saveUser(User user) {
        // handler method to save/update a user
        userDAO.save(user);
    }

    public String deleteUser(User user) {
        // handler method to delete a user
        userDAO.delete(user);
    }

    public String getUser(int userId) {
        // handler method to get a user
        userDAO.get(userId);
    }
}
  • Create each separate controller for each business domain. For example, UserController for controlling workflows of the user management, OrderController for controlling workflows of order processing, etc. For example:
@Controller
public class UserController {

}

@Controller
public class ProductController {

}

@Controller
public class OrderController {

}

@Controller
public class PaymentController {

}

There you have it! I have shared 14 tips that will help you write controller classes in Spring MVC properly and efficiently. However, that’s not the end. If you have other tips or suggestions, feel free to share your thoughts in the comments.

Visually tests for Vue.js Storybook

Visually tests for Vue.js Storybook

Over the last couple of weeks, I've found new joy with writing my Vue.js components within Storybook as a tool to visualise all the possible permutations of a given Component in isolation from the target application.

It's all fair game writing your code, hitting save and seeing the change in the browser and visually observing everything works as expected. That's not good enough! I want unit-tests to ensure my components functionality is what I expect.

In this guide, I'll show you how to install Jest to your Storybook project and examples of tests for Vue.js components.

Getting started

If you already have Storybook and Vue.js installed to your project, please skip to Installing Jest

Let's get you quickly started with Storybook and Vue.js by creating a new project folder where your stories will reside.

Make a new folder; here we'll call it design-system but you can call it whatever you like.

mk ./design-system
cd ./design-system

Now we'll install our main dependencies Vue.js and Storybook.

note: My personal preference is the Single File Component style of Vue.js for ease of understanding between projects.

npm init -y # initialize a new package.json quicly
npm install --save vue
npm install --save-dev vue-loader vue-template-compiler @babel/core [email protected]^7.0.0-bridge.0 babel-loader babel-preset-vue
npx -p @storybook/cli sb init --type sfc_vue

Hooray! We've got Storybook installed with a couple of Vue.js examples to start.

Let's boot the Storybook server and see what we got.

npm run storybook

That is great and all, but now we'll want to set up Jest.

Installing Jest

Let's get stuck right in and install all the dependencies required.

npm install --save-dev jest vue-jest babel-jest @babel/core @babel/preset-env @vue/test-utils

Configure Babel by creating a babel.config.js file in the root of the project.

// babel.config.js
module.exports = {
  presets: [
    '@babel/preset-env'
  ]
}

Configuration for Jest will need to be added too by creating a jest.config.js file in the root of the project.

// jest.config.js
module.exports = {
  moduleFileExtensions: ['js', 'vue', 'json'],
  transform: {
    '^.+\\.js/div>: 'babel-jest',
    '.*\\.(vue)/div>: 'vue-jest'
  },
  collectCoverage: true,
  collectCoverageFrom: ['/src/**/*.vue'],
  transformIgnorePatterns: ["/node_modules/([email protected]/runtime)"],
  coverageReporters: ["text-summary", "html", "lcov", "clover"]
}

Finally, we'll need to update the package.json scripts to reference Jest as our test runner.

// package.json
{
  "name": "storybook-vue",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook"
  },
  ...
}

Before we continue, let's give our installation a quick run to ensure everything is looking ok.

We'll have to run Jest with --passWithNoTests as we haven't written any tests yet.

note: the double dashes -- on their own are intentional to allow the arguments to be passed through to the inner command.

npm run test -- --passWithNoTests

We should see the following output.

npm run test -- --passWithNoTests

> [email protected] test ~/code/design-system
> jest "--passWithNoTests"

No tests found, exiting with code 0

=============================== Coverage summary ===============================
Statements   : Unknown% ( 0/0 )
Branches     : Unknown% ( 0/0 )
Functions    : Unknown% ( 0/0 )
Lines        : Unknown% ( 0/0 )
================================================================================

Great!, everything looks like it's wired up ok for Jest to be happy, now let's write some tests.

Writing our first test

Given we set up the project fresh and ran the initialise command in Storybook, we should have some simple example stories waiting for us in src/stories.

For example, our project structure would look something like this.

tree -I 'node_modules|coverage'
.
|-- babel.config.js
|-- jest.config.js
|-- package-lock.json
|-- package.json
`-- src
    `-- stories
        |-- 0-Welcome.stories.js
        |-- 1-Button.stories.js
        |-- MyButton.vue
        `-- Welcome.vue

2 directories, 8 files

Create a new file in the src/stories directory called MyButton.test.js so we can write our first tests for MyButton.vue.

In this test file, we'll import the MyButton.vue component and @vue/test-utils.

// src/stories/MyButton.test.js
import Component from './MyButton.vue';
import { shallowMount } from "@vue/test-utils";

describe('MyButton', () => {
  let vm
  let wrapper
  beforeEach(() => {
    wrapper = shallowMount(Component)
    vm = wrapper.vm
  })
})

Looking at our MyButton.vue file, we'll see in the `` block a method called onClick.

// src/stories/MyButton.vue (fragment)
export default {
  name: 'my-button',

  methods: {
    onClick () {
      this.$emit('click');
    }
  }
}

This method, when called, will emit a click event to any parent consuming components. So testing this will require us to spy on $emit, and we will expect $emit to be called with click.

Our test will look like the following.

// src/stories/MyButton.test.js (fragment)
describe('onClick', () => {
  it('emits click', () => {
    vm.$emit = jest.fn()
    vm.onClick()
    expect(vm.$emit).toHaveBeenCalledWith('click')
  })
})

Here's a full example of our MyButton.vue.js test file.

// src/stories/MyButton.test.js
import { shallowMount } from "@vue/test-utils";
import Component from './MyButton.vue';

describe('MyButton', () => {
  let vm
  let wrapper
  beforeEach(() => {
    wrapper = shallowMount(Component)
    vm = wrapper.vm
  })

  describe('onClick', () => {
    it('emits click', () => {
      vm.$emit = jest.fn()
      vm.onClick()
      expect(vm.$emit).toHaveBeenCalledWith('click')
    })
  })
})

Brilliant! We can run our tests and see how we're doing.

npm run test

> [email protected] test ~/code/design-system
> jest

 PASS  src/stories/MyButton.test.js
  MyButton
    onClick
      ✓ emits click (15ms)

=============================== Coverage summary ===============================
Statements   : 25% ( 1/4 )
Branches     : 100% ( 0/0 )
Functions    : 33.33% ( 1/3 )
Lines        : 25% ( 1/4 )
================================================================================
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.921s
Ran all test suites.

Congratulations you've just written our first test for our Storybook project!

... but what is that in the Coverage summary? 25% of the lines are covered? That has to be improved.

Improving code coverage

As we did with our first test, we'll create a new file for the other component Welcome.test.js in the src/stories directory.

The contents of Welcome.vue is a little more involved with props and having to preventDefault.

// src/stories/Welcome.vue
const log = () => console.log('Welcome to storybook!')

export default {
  name: 'welcome',

  props: {
    showApp: {
      type: Function,
      default: log
    }
  },

  methods: {
    onClick (event) {
      event.preventDefault()
      this.showApp()
    }
  }
}

Let's cover the natural part first, methods as with the tests in MyButton.test.js we can copy most of this code across.

As our code stipulates, we'll need to spy on the given property showApp to ensure it is called and the event we provide will have to include preventDefault.

// src/stories/Welcome.test.js (fragment)
describe('onClick', () => {
  it('calls showApp', () => {
    let showApp = jest.fn()
    wrapper.setProps({
      showApp
    })
    let event = {
      preventDefault: jest.fn()
    }
    vm.onClick(event)
    expect(showApp).toHaveBeenCalled()
    expect(event.preventDefault).toHaveBeenCalled()
  })
})

Testing props have a subtle difference to it as we need to fully mount the component to access the $options where props are defined.

// src/stories/Welcome.test.js (fragment)
describe("props.showApp", () => {
  it('logs message', () => {
    wrapper = mount(Component)
    vm = wrapper.vm
    let prop = vm.$options.props.showApp;

    let spy = jest.spyOn(console, 'log').mockImplementation()
    prop.default()
    expect(console.log).toHaveBeenCalledWith('Welcome to storybook!')
    spy.mockRestore()
  })
})

Ensure to import mount from @vue/test-utils

// src/stories/Welcome.test.js (fragment)
import { shallowMount, mount } from "@vue/test-utils";

You would notice we're using jest.spyOn() to mock the implementation of console.log to allow us to assert .toHaveBeCalledWith and then restore the console.log to its initial application once our test has completed.

Here is a full example of the test file.

// src/stories/Welcome.test.js
import { shallowMount, mount } from "@vue/test-utils";
import Component from './Welcome.vue';

describe('Welcome', () => {
  let vm
  let wrapper
  beforeEach(() => {
    wrapper = shallowMount(Component)
    vm = wrapper.vm
  })

  describe("props.showApp", () => {
    it('logs message', () => {
      wrapper = mount(Component)
      vm = wrapper.vm
      let prop = vm.$options.props.showApp;

      let spy = jest.spyOn(console, 'log').mockImplementation()
      prop.default()
      expect(console.log).toHaveBeenCalledWith('Welcome to storybook!')
      spy.mockRestore()
    })
  })

  describe('onClick', () => {
    it('calls showApp', () => {
      let showApp = jest.fn()
      wrapper.setProps({
        showApp
      })
      let event = {
        preventDefault: jest.fn()
      }
      vm.onClick(event)
      expect(showApp).toHaveBeenCalled()
      expect(event.preventDefault).toHaveBeenCalled()
    })
  })
})

We can rerun our tests and fingers crossed the coverage should be vastly improved. 🤞

npm test

> [email protected] test ~/code/design-system
> jest

 PASS  src/stories/MyButton.test.js
 PASS  src/stories/Welcome.test.js

=============================== Coverage summary ===============================
Statements   : 100% ( 4/4 )
Branches     : 100% ( 0/0 )
Functions    : 100% ( 3/3 )
Lines        : 100% ( 4/4 )
================================================================================

Test Suites: 2 passed, 2 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        2.404s
Ran all test suites.

That is Awesome, well done!

Notes

With most code challenges, I usually battle through small problems along the way. Here I like to give credit to where I have found solutions to the issues I have experienced while getting the project setup.

Using Jest with Babel as documented required adding [email protected] to the development dependencies to ensure it works well with Babel 7.

You'll notice in the jest.config.js I included a transformIgnorePatterns definition. Although the current code doesn't demand too much from Core.js, I added this definition. It will save some headake later on in your development, avoiding the no descriptive SyntaxError: Unexpected identifier issues.

Thank you for reading, I hope this helped you get your Vue.js Storybook project to the next level.

Spring Boot Tutorial For Beginner- Spring Boot Full Course

Spring Boot Tutorial For Beginner- Spring Boot Full Course

Spring Boot Full Course - Learn Spring Boot In 4 Hours | Spring Boot Tutorial For Beginner

Below are the topics covered in this Spring Boot Tutorial for Beginners video:

1:40 What is Spring Boot?
2:35 Features of Spring Boot
3:50 Why Do We Need Spring Boot?
4:30 Spring Boot Market Trend
5:15 Spring vs Spring Boot
6:25 Install & Setup Spring Boot
6:45 System Requirements
7:35 Install & Set up Spring Boot CLI
14:00 Install & Setup Spring Tool Suite
25:40 Model View Controller
26:00 What is MVC?
27:35 Model View Controller Workflow
29:00 What is Dependency Injection?
31:50 Inversion of Control
33:10 Types of Dependency Injection
34:05 Benefits of Dependency Injection
48:35 Auto wire
49:50 Create First Web Application Using Spring Boot
1:06:50 Create a Web Application To Pass Client Data
1:13:40 Model View & Object Example
1:20:30 Create a Submission Form In Spring Boot
1:40:50 Connect Web Application To Database
2:04:50 REST API
2:07:35 What is REST API?
2:08:50 Features of REST API
2:09:35 Principles of REST API
2:11:40 Methods of REST API
2:12:20 REST API Hands-On
2:35:55 Spring Data REST
2:36:55 Spring Data REST Hands-On
2:46:35 Spring Security
2:47:30 Secure Applications Using Spring Boot
2:58:56 Spring Boot Interview Questions

<iframe width="560" height="315" src="https://www.youtube.com/embed/UfOxcrxhC0s" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

Spring Boot Tutorial for Beginners (Java Framework)

Spring Boot Tutorial for Beginners (Java Framework)

Spring Boot is an amazing framework for building Java applications. It makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run".

<iframe class="ql-video" frameborder="0" allowfullscreen="true" src="https://www.youtube.com/embed/vtPkZShrvXQ?showinfo=0"></iframe>

Thanks for reading

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

Follow us on Facebook | Twitter

Further reading

Spring & Hibernate for Beginners (includes Spring Boot)

Spring Framework Master Class - Learn Spring the Modern Way!

Master Microservices with Spring Boot and Spring Cloud

Spring Boot and OAuth2: Getting the Authorization Code

An Introduction to Spring Boot

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.

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 —

<pre class="ql-syntax" spellcheck="false">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"
}
</pre>

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 DELETEPackages to use for Unit Testing in Go

  1. 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 — 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.
NOTE: Go avoids the use of assertions.

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 -
<pre class="ql-syntax" spellcheck="false">func TestGetEntries(t *testing.T) {
....
}
</pre>

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—

<pre class="ql-syntax" spellcheck="false">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)
}

}
</pre>

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 —

  • Line No. 1- Please note the function’s name here. It starts with Test so that the testing package recognizes the test case and the next words in the camel case also starts with an uppercase. The function has a parameter which is a pointer to the testing package’s T variable which signifies that it is a test case.
  • Line No. 2- This creates a new request to the /entries endpoint.
  • Line No. 6- Creates a new recorder to record the response received by the entries endpoint.
  • Line No. 8- Hits the endpoint with the recorder and request.
  • Line No. 9- Checks if the response is 200 OK.
  • Line No. 10- Sends an error tagging as a test failure.
  • Line No. 15- Is an expected output from the endpoint.
  • Line No. 16- Check if the response is equal to expected.

GetEntryByID Test Case-

<pre class="ql-syntax" spellcheck="false">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)
}

}
</pre>

GetEntryByIDNotFound Test Case-

<pre class="ql-syntax" spellcheck="false">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)
}
}
</pre>

CreateEntry Test Case-

<pre class="ql-syntax" spellcheck="false">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)
}

}
</pre>

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-

<pre class="ql-syntax" spellcheck="false">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)
}

}
</pre>

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

DeleteEntry test case-

<pre class="ql-syntax" spellcheck="false">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)
}
}
</pre>

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.

Thanks for reading ❤

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

Follow me on Facebook | Twitter

Learn More

Learn How To Code: Google’s Go (golang) Programming Language

Go: The Complete Developer’s Guide (Golang)

Build Realtime Apps | React Js, Golang & RethinkDB

Golang Tutorial: Learn Golang by Examples

Build a chat app with Go

Google’s Go Essentials For Node.js / JavaScript Developers

Test Automation Using Pytest and Selenium WebDriver

Top 5 Java Test Frameworks for Automation in 2019

Learn Selenium Automation with Robot Framework from scratch

Building A REST API With MongoDB, Mongoose, And Node.js

Building REST API with Nodejs / MongoDB /Passport /JWT