惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cloudbric
Cloudbric
T
Threat Research - Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
P
Privacy & Cybersecurity Law Blog
H
Help Net Security
云风的 BLOG
云风的 BLOG
G
GRAHAM CLULEY
Spread Privacy
Spread Privacy
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
A
Arctic Wolf
Project Zero
Project Zero
Engineering at Meta
Engineering at Meta
P
Privacy International News Feed
Blog — PlanetScale
Blog — PlanetScale
Stack Overflow Blog
Stack Overflow Blog
M
MIT News - Artificial intelligence
The Register - Security
The Register - Security
Recorded Future
Recorded Future
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
C
Cisco Blogs
PCI Perspectives
PCI Perspectives
Recent Announcements
Recent Announcements
Martin Fowler
Martin Fowler
A
About on SuperTechFans
W
WeLiveSecurity
GbyAI
GbyAI
V
Vulnerabilities – Threatpost
The GitHub Blog
The GitHub Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Check Point Blog
Y
Y Combinator Blog
月光博客
月光博客
Scott Helme
Scott Helme
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
U
Unit 42
G
Google Developers Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Threatpost
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google Online Security Blog
Google Online Security Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
Hugging Face - Blog
Hugging Face - Blog
MongoDB | Blog
MongoDB | Blog
博客园 - 司徒正美

Philip Walton

The State of ES5 on the Web Dynamic LCP Priority: Learning from Past Visits Performant A/B Testing with Cloudflare Workers My Challenge to the Web Performance Community Smaller HTML Payloads with Service Workers Cascading Cache Invalidation Using Native JavaScript Modules in Production Today KV Storage: the Web's First Built-in Module Idle Until Urgent Page Lifecycle API First Input Delay Responsive Components: a Solution to the Container Queries Problem Why Web Developers Need to Care about Interactivity Deploying ES2015+ Code in Production Today How We Track Pageviews Is All Wrong The Google Analytics Setup I Use on Every Site I Build The Dark Side of Polyfilling CSS Loading Polyfills Only When Needed Untangling Deeply-Nested Promise Chains Learning How to Set Up Automated, Cross-browser JavaScript Unit Testing Houdini: Maybe the Most Exciting Development in CSS You've Never Heard Of Why I'm Excited About Native CSS Variables Do We Actually Need Specificity In CSS? How to Become a Great Front-End Engineer Extending Styles Side Effects in CSS Normalizing Cross-browser Flexbox Bugs Measuring Your Site's Responsive Breakpoint Usage The Dangers of Stopping Event Propagation Stop Copying Social Code Snippets Implementing Private and Protected Members in JavaScript How to Find Qualified Developers Interviewing as a Front-End Engineer in San Francisco Solved by Flexbox Decoupling Your HTML, CSS, and JavaScript Why I Test Private Functions In JavaScript Introducing HTML Inspector CSS: Everything is Global and How to Deal With It Dynamic Selectors Defending Presentational Class Names The Future of OOCSS: A Proposal What No One Told You About Z-Index CSS Architecture
How to Unit Test Private Functions in JavaScript
2013-07-09 · via Philip Walton

Update: A lot of readers have commented or tweeted that I shouldn’t be unit testing private functions in the first place. Admittedly, this article is a little light on the why and mainly focuses on the how.

To explain some of my rationale, I wrote a follow-up article: Why I Test Private Functions In JavaScript

JavaScript’s closures provide an excellent way to make variables and functions private, keeping them out of the global scope. This is particularly important in the browser because all scripts share the same scope, and it’s quite easy to inadvertently pick a variable or function name used by another library.

The problem, however, is that when functions are hidden inside a closure, it’s very difficult to test them.

Here’s an example:

var myModule = (function() {

  function foo() {
    // private function `foo` inside closure
    return "foo"
  }

  return {
    bar: function() {
      // public function `bar` returned from closure
      return "bar"
    }
  }
}())

The immediately invoked function expression returns an object that exposes the bar function globally, but the foo function remains inaccessible to any code not appearing inside the immediately invoked function.

In other words, given the above code, writing a unit test for bar is easy, but writing a unit test for foo is impossible. So what can you do? How can you test foo?

If you search Stack Overflow for a solution to this problem, you’ll basically find the following two suggestions repeated over and over again:

  1. Don’t test private functions (since they’re implementation details)
  2. If you must test them, make them public

Personally, I don’t agree with these statements.

While they may apply to some contexts, they certainly don’t make sense for every project. And from my own experience writing JavaScript, I’ve encountered numerous situations where I needed to test a function but still wanted it hidden from the public API.

I believe any real solution to this problem shouldn’t require developers to compromise their design patterns for the sake of testing, nor should it require lesser test coverage just to produce a nicer API. We should be able to have it both ways.

Below is the solution I use that I think handles these issues quite nicely.

My Solution

Given the nature of JavaScript scoping, we know it’s impossible to access local variables from outside of a function closure without a reference to those variables. So any possible solution must do one of two things:

  1. Put the test code itself inside the closure
  2. Add code inside the closure that stores references to the local variables on existing objects in the outer scope.

Option one is not really a good solution because you usually want to keep all your test code together in the same place. Mixing your tests with the production code would make it significantly harder to maintain and organize.

On the other hand, option two kinda defeats the purpose of encapsulating the code in the first place.

But what if there were a way to use option two conditionally? What if we could write code inside a closure that gives our test code access to the private functions we care about, but then remove that code when we deploy to production?

If you’re using a build system, you can do exactly that.

Different Builds for Testing and Production

Consider the above myModule example with the following code additions (changes highlighted):

var myModule = (function() {

  function foo() {
    // private function `foo` inside closure
    return "foo"
  }

  var api = {
    bar: function() {
      // public function `bar` returned from closure
      return "bar"
    }
  }

  /* test-code */
  api._foo = foo
  /* end-test-code */

  return api
}())

As you can see, the above changes expose the foo function on the module, but do so in a way that can be easily identified by the surrounding comments and stripped out later.

The strategy here is to write your code exactly how you’d like it to appear when released, then add on whatever bridge code you need to expose the parts you want to test.

An Example Implementation

Assuming you’re already using a build system (if you’re not I highly recommend doing so), adding an extra step to strip out test-only code blocks is relatively easy. Below is an example of how you could implement this using GruntJS and the grunt-strip-code plugin.

First, make sure to add the task as a dependency to your project:

npm install grunt-strip-code --save-dev

Then enable it inside your Gruntfile:

grunt.loadNpmTasks('grunt-strip-code');

Next, add your project specific data to the initConfig object:

grunt.initConfig({
  strip_code: {
    options: {
      start_comment: "test-code",
      end_comment: "end-test-code",
    },
    your_target: {
      // a list of files you want to strip code from
      src: "dist/*.js"
    }
  }
})

Finally, add your new task to the deploy step (making sure not to add it to the testing step):

grunt.registerTask("test", [
  "concat",
  "jshint",
  "jasmine"
])
grunt.registerTask("deploy", [
  "concat",
  "strip-code",
  "jshint",
  "uglify"
])

Now, when you test your code, those test-only blocks will be in the source like you want, but they’ll be stripped out when you deploy to production.

That means you can still maintain 100% test coverage without sacrificing your code structure or design.

It’s the best of both worlds.

A Word of Caution

There are a lot of pros to this approach, and I certainly favor it over exposing your private functions in an awkward way or not testing them at all, but there is a downside that you should be aware of.

Anytime you change your code between when it’s tested and when it’s deployed, you run the risk of missing something. If you weren’t careful and part of your library depended on the code you removed, your library would break in production, and you might not know about it until long after it shipped.

Luckily, there are two relatively simple strategies to help prevent this. The easiest way is to assign your test only functions to an object namespace that is obviously not intended for normal use. For example:

/* test-code */
api.__testonly__.foo = foo
/* end-test-code */

Using a namespace like __testonly__ not only makes the intent obvious, it also makes it possible to add an additional step to the build process to make sure no __testonly__ code appears in the final release.

Another strategy is to split your test suite into two phases. The first phase tests the private functions. The second phase strips the code and then tests the public functions. Here’s an example of that:

grunt.registerTask("test", [
  "concat",
  "jshint",
  "jasmine:private",
  "strip-code",
  "jshint",
  "jasmine:public"
])

Though not always possible (depending on your test setup), this is probably the safest method as it still tests the public API after stripping out the test-only code.

These two strategies may not be necessary if you’re the only developer or working on a small team, but the more people you have contributing the more caution you’ll need to take. Obviously it’s up to you to determine the right approach for your situation.

Conclusion

To summarize, due to the nature of JavaScript it’s important to not expose too many variables and functions in the global namespace. Using function closures and the module pattern is a great way to hide functionality, but it also makes it harder to test that functionality.

Even though that code may be an implementation detail, merely testing the public API may not give you the peace of mind you need when making code changes in the future.

Testing your code is important, even if it takes a little bit of extra work. This article shows you how you can use a build system to test your private functions without compromising encapsulation and modularity.