Thursday, April 07, 2016

Testing React components with Jasmine in Rails with the react-rails gem

So in my latest project, we are using Ruby on Rails and the react-rails gem to implement our React components.

While react-rails is fine for the most part (and for simple components), there is a severe limitation in that you cannot use ES6 modules (i.e. using the import and export classes) because of the way asset-pipeline works.

Asset-pipeline precompiles all your JS into one mega JS file and therefore doesn't allow you to call individual JavaScript components in separate files (because they don't pushed to the server). This is a severe problem which I hope gets fixed soon, but in the meantime don't hold your breath.

There are workarounds using Browserify and/or Webpack, but they are messy (Google them for more info).

In any case, react-rails does allow simple React components in Rails apps and it mounts them all in the window/global namespace, so you can use a good 75% of React within a Rails application.

So now, we come to testing...

Facebook uses a testing framework called Jest (https://facebook.github.io/jest/docs/tutorial-react.html). It looks pretty good, but unfortunately does require the use of the import/export syntax...

But hey, React components are just JavaScript right? That means you can test them with any library (like Jasmine https://github.com/jasmine/jasmine-gem). Right?

Well, they are JavaScript, but they are also kind of closed off and it can be hard to actually unit test and mock them. I created a React component called DailyQuiz and for testing I tried to simply instantiate it normally.

var dailyQuiz = new DailyQuiz();

I was then able to see all the attributes on dailyQuiz in the console as if it were a regular JS object.

Until that is, I ran into setState in the code. Unfortunately, it gave me some guff about

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the  component.

So, basically, this means we can't treat a React component as a plain JS object because there is a lot happening behind the scenes. In a way this makes sense because React does a lot of calculations before outputting to the DOM, so these restrictions are probably needed.

So rather than trying to fake the whole mounting of the component, I decided it would be better to actually mount it. I used jasmine-jquery (https://github.com/travisjeffery/jasmine-jquery-rails) to help me manipulate the DOM with jasmine.

I will show you the code snippet and then talk you through it.
  
  beforeEach(function() {
    deferred = new jQuery.Deferred();
    spyOn($, 'ajax').and.returnValue(deferred);
    setFixtures('[div id="react-fixture"]
test[/div]
');

    dailyQuiz = ReactDOM.render(
      React.createElement(DailyQuiz, {
        url: "/",
        saveUrl: "/save"
      }),
      document.getElementById('react-fixture')
    );

    deferred.resolve(data);
  });

* note for this example I had to replace the < and > with [ and ] for rendering purposes... Blogger is giving me some hassle...

So basically I am using $.ajax with the promise syntax and then spying on it so I can intercept the ajax call in my spec. I then use jasmine-jquery's setFixtures to create a place to mount the component, I mount the component with plain JS (not JSX) and the resolve the AJAX call.

From there, you can actually call dailyQuiz and it will reflect the state of the component.

The downside of this approach is that the data you use to populate your component has to actually work (you can't just stub any old data through it). There may be a way to spy on the subcomponents, but I haven't found a way yet.

The benefits are that you can test the actual state of the actual component. It's just not a headless component (it needs to be mounted). You can also use jasmine-jquery to test the DOM meets expectations.

Thursday, March 17, 2016

Rolify, Devise, Rspec and Capybara Webkit in Rails

So I was using Rolify, Devise, Rspec and Capybara Webkit in Rails and I ran into a problem with the Warden login_as helper when trying to do a feature spec with :js => true (if you didn't understand any of that, you are probably on the wrong blog).

Anyways, while the I was able to log in an admin, Rolify was not able to determine the roles. This is in spite of the fact that I added a role in Factory Girl.

FactoryGirl.define do
  factory :admin do
    email { Faker::Internet.email }
    password "password"
    password_confirmation "password"

    trait :administrator do
      after(:create) {|admin| admin.add_role(:administrator)}
    end
  end
end

This worked fine with js turned off, so why was it not working now?

So the answer had to do with the following line in rails_helper.rb

config.use_transactional_fixtures = true

So I believe what happened in Capybara webkit is that it runs in a different thread and while Warden's login_as worked fine in a regular spec (as the admin object is passed into memory), the role information (which needs a database lookup) was not passed through and hence when I was checking that the admin was an administrator (current_admin.has_role? :administrator) it failed.

So here's the fix

1) Install the database_cleaner gem

2) set transactional fixtures to false
config.use_transactional_fixtures = false

3) Set up database cleaner as per http://devblog.avdi.org/2012/08/31/configuring-database_cleaner-with-rails-rspec-capybara-and-selenium/

4) in your rails_helper.rb
require 'support/database_cleaner'

Your Capybara Webkit Rolify specs should now work with the Warden helpers.

PS Sorry this isn't that well written. This is primarily a reminder for myself (I am still in mid project), but I thought I would post it in case anyone else ran into the same issue.

Thursday, July 02, 2015

Global Warming is a Year 2000 problem

Global Warming is a Year 2000 problem.

What I mean is not that Global Warming has been solved (far from the case) nor that it is a problem that we could only have solved 15 years ago (though that might be closer to the truth).

What I mean is that if we solve Global Warming, then people will think it was a hoax (or something we should not have worried about).

Think back to the glory days of the 1990s. Media was full of stories about how all the world's computer systems were going to fail because programmers tried to save space on their systems by dropping the first 2 digits off the year field. Power stations and ATMs would stop working. Global financial crises would ensue (well, that did happen, but for other reasons...). Basically we were going back into the stone age unless we chucked large amounts of money at software developers to fix our computer systems and by and large that did happen.

After we partied like it was 1999, apparently nothing happened. I am sure a couple of systems failed, but nothing major. We were all still alive and we still had electricity and we could still get money out of ATMs.

Post 2000, everyone now thinks the whole thing was just a bunch of hooey.

But maybe we did avert major disaster by all our hard work. We will never really know.

I hope to an extent the same thing does happen with Global Warming. There are differences of course. We have not got a set date where the planet will be uninhabitable. A date when we can look back and laugh at ourselves for being so silly.

Instead, we will look back at ourselves in shame for being so oblivious (and remember what things used to be like).

There's probably not much we can do on an individual level. We will need to elect people who are aware of the problem and reach consensus (easier said than done).

Let's just hope that in 7984 years we have a Year 10,000 problem to deal with ("Why on earth did they think they could save space by only allocating 4 digits to a year?").

Wednesday, June 17, 2015

Source Location, i.e. Where the heck is this method defined?

Just a short one for now.

Most of the time, if you are working on a well defined project, it's pretty easy to find where a function is if you want to change/tweak it. You can look in the class where it is defined and/or see if there are any mixins or decorators.

Sometimes, it can be downright impossible to tell.

Enter source_location

Fire up your rails console and put in the following (for a class method :search on User)

User.method(:search).source_location

if you have an instance method

User.new.method(:search).source_location

This should give you something like the following

["/Users/yourdrive/Projects/prokect/vendor/cache/thinking-sphinx-9b71ce0f7d9d/lib/thinking_sphinx/search_methods.rb",

 367]

This should at least give you some idea of what is going on...

Sunday, June 14, 2015

Mixins in ES6

I had been looking for a good way to do mixins in ES6.

Looks like I finally found one.

http://raganwald.com/2015/06/10/mixins.html

Essentially,

create the mixin as a const

Use Object.assign to add the properties to the Class

const BookCollector = {
  addToCollection (name) {
    this.collection().push(name);
    return this;
  },
  collection () {
    return this._collected_books || (this._collected_books = []);
  }
};

class Person {
  constructor (first, last) {
    this.rename(first, last);
  }
  fullName () {
    return this.firstName + " " + this.lastName;
  }
  rename (first, last) {
    this.firstName = first;
    this.lastName = last;
    return this;
  }
};

Object.assign(Person.prototype, BookCollector);
(I am copying and pasting this for personal reference in case the original site link goes down).