Tony D Tony D - 3 months ago 22
Javascript Question

How to unit test javascript ES6 classes that encapsulate their own creation within a static function?

In Dan Schafer's React Europe talk (https://youtu.be/etax3aEe2dA?t=11m5s), he showed one approach to handling authorization in a GraphQL server could be delegating to a business logic layer in which each business object class has a gen() function that acts as the "single source of truth" for both fetching and authorization.

He implies that these classes could have no public constructor, thereby making gen() the only way to instantiate one of these classes.

As a C# dev, this wouldn't be [easily] unit-testable. Perhaps, I'm taking his example too literally.

In ES2015, is it possible to unit test the business logic in isolation with this pattern? If so, can you provide an example please?




Here's a related Q&A, but it doesn't address my question exactly:

Q: With GraphQL, what is the benefit or necessity of declaring a backing model "class" that does not vary from the GraphQL "Type" in any obvious way?

A: http://stackoverflow.com/a/38209996/6439734

Answer

The approach I'd take would vary depending on what I was trying to test:

  • If I was trying to unit test my authorization logic itself, I would almost certainly factor that logic out into something that looks like: Code sample from slide from Dan Schafer React Europe 2016 Talk Now I would just unit test checkCanSee completely on its own, passing in a fake Viewer object and fake data.

  • If I was trying to unit test the logic in one of the instance methods of this class; say, if we had class TodoItem { getItemSummary() { return this.data.title + "(" + this.data.author.name + ")"; } } and I wanted to unit test that logic, there are a few options. In ES6, I'm not aware of an easy way to make the constructor actually private, so I could just call the constructor directly in my unit test with fake data. In another language, I'd hopefully be able to expose a method that was only visible by unit tests that would let me construct a new object with fake data. A third idea would be to extract out the unit test-worthy logic: class TodoItem { getItemSummary() { return summarize(this.data.title, this.data.author.name); } } function summarize(title, name) { return title + "(" + name + ")"; } Now I could unit test summarize on its own.

  • If I was trying to end-to-end test the whole class (which includes testing the gen function), I'd ideally have a way of setting up a shimmed instance of Redis; once I configured that shim with my fake data, I can just run the real TodoItem.gen and test the resulting object.

  • If I wanted to unit test the implementation of gen, I'd probably need to use that same Redis shim above, and similarly install a fake version of checkCanSee, using something like https://facebook.github.io/jest/docs/manual-mocks.html; from there, I could spy the call to Redis and to checkCanSee to make sure they are as expected, and test the various cases where Redis returns data/returns null or checkCanSee returns true/false.

  • If I wanted to unit test code that consumes a TodoItem (say we have a function renderTodoItem that takes a TodoItem as an argument), I wouldn't use the real TodoItem implementation at all, I'd pass in a mock TodoItem to make sure that my unit test of renderTodoItem doesn't accidentally rely on implementation details of TodoItem.

Hope this helps!