Best Practice: Using NodeJS for web Development, How to Gracefully Perform Unit Testing of Data Layer?

  node.js, question

After careful consideration, I think the dimension of this question I asked can be large or small. I will list some of my troubles and ask for suggestions or share experiences.

How to determine the dimension of the test?

Take a common web application as an example, we usually have a data layer (completely encapsulating the operations of the database), a client-oriented business interface, and possibly an API for third-party applications. So what dimensions do you normally test? For example:

  • Do unit tests on the data layer?
  • Http Interface testing for apis?

The tangle mainly lies in the fact that although the tests are conducted at different levels, there are many honors and honors in it. Such as one of the data layerscreateUserThe test of the method, for the HTTP layer of the API, is simply to pass user input directly into the method and then return the result of the method to the client.

What do you usually do with the specific preparation of the test?

At present, these tests are all written by hand. I don’t know if there is any more automated way to do these tests. In writing, I personally feel that the problems are:

  • Mechanical Repetitive Labor: For example, in your businessuser,app,projectThese examples, then the so-called three“Add, delete, change and check”The use cases are basically the same, and you may finish themuserAfterappAndprojectBasically, it was copied and changed its name.
  • Complicated Construction of Analog Data
  • Lack of guidance on best practices, such as the following:
jsdescribe( 'common', function(){
 It ('add user', function( done ){
 It ('get user list', function( done ){
 It ('get a user', function( done ){
 It ('update user', function( done ){
 It ('delete user', function( done ){

The feeling should be a more common case, so besides the first one“Add User”In addition, it can be seen that all other case are required“A user has been added”On the premise that. So how do you usually operate? I think of two ideas:

  • Scheme 1: add a new user independently in each case, and delete the user at the end (you can use mocha’sbeforeEachAndafterEach)
  • Scheme 2: Create a global new user variable. After the first case runs, copy it into this variable. Subsequent CASEs can directly use this variable.

Welcome to share your experience! ! ! !

In my experience:

  • The test should be based on “business process” rather than “interface”
  • Do not share data between different tests and do not use global variables as much as possible
  • As far as possible, business logic should be written in Model inside (or abstract a Service layer separately), not in the Controller.
  • If it is found that there are too many repeated tests, you can not write the test (or only write the test in one place) first, and then write the test for the problem when it is found that there is a problem with a certain function.

The so-called “business process” refers to a process similar to “an ordinary user registers an account, logs in, modifies a password, posts and replies” or “an administrator account logs in to the administrator panel, posts, replies and deletes other people’s posts”. There are many benefits such as sharing the same “normal user” for each test in the first process and sharing a “administrator user” for the second process.

It is more controllable to share data within a process, because a process is generally not too long and the internal connections are relatively large. If testing is done for each interface, either the data is prepared separately for each test (very tedious), or the data is shared among all tests (there will be many global variables, which will greatly increase the complexity, and the tests may interfere with each other).

It should be ensured that the tests of each test file inside are independent. Use before to define the data on which this file depends (for example, all tests of this file need to have a user first). You can define one in other files.createTestAccountFunction, accept some options, and then generate test data that meets the requirements. The advantage of this is that you can run a test alone. If running the whole test is time consuming, it can solve a lot of time, and you don’t have to worry about interference between tests. Moreover, libraries such as mocha do not guarantee the running order between different test files (although they actually run alphabetically). It is very troublesome if there are dependencies between different test files.

As to whether to test API interface or Model, this is quite different, but “try to write business logic to Model” inside will not change.

If it is a test API, then the test can directly call the method of Model inside to prepare the data and verify the test results (it is much more convenient than using API to prepare the data and verify the results); If the Model is measured, since most of the logic is in the Model inside, it can basically ensure that there is no big problem after the Model is measured, because Controller inside does not have much logic.

Finally, be sure not to use the Code Generator to generate test code. It can be generated by scripts, which shows that there is inherent logic between these tests. You can completely avoid duplicate codes through good design. After all, mocha tests are all JavaScript codes. Why can’t they be implemented? Of course, the degree of abstraction of the test should not be too high, and it should be a little more straightforward, otherwise too much time will be spent on “debugging the test”, which needs to be weighed by itself.

Please refer to for “code generator”The Way Programmers Practise: From Child Worker to ExpertIn the “evil guide” section of the book, this book also introduces a large number of techniques for writing automatic tests.