(apparently) identical tests for two rake tasks; only one passes

Nutshell: Change your before to a before :all (instead of :each).

Or: Pass an empty array as a third parameter to rake_require.

Rake.application.rake_require 'lib/tasks/demo_tasks', 
                              [Rails.root.to_s], 
                              []

Details

def rake_require(file_name, paths=$LOAD_PATH, loaded=$")
  fn = file_name + ".rake"
  return false if loaded.include?(fn)
  ...

$" is a Ruby special variable that holds an array of modules loaded by require.

If you don't pass the optional parameter, rake_require will use that array of modules loaded by Ruby. This means the module won't be loaded again: Ruby knows the module was loaded, rake checks to see what Ruby knows, and it's a new rake instance for each test.

Switching to before :all worked because it meant the let block only ran once: one rake instance, one module load, everybody is happy.

All this said, why reload the rake environment twice anyway? Your goal is to test your tasks, which doesn't require a fresh rake context for every spec.

You could eliminate the local altogether at the cost of some minor verbosity in each spec:

describe "test tasks" do
  before :all do
    Rake.application = Rake::Application.new
    Rake.application.rake_require 'lib/tasks/demo_tasks', [Rails.root.to_s]
    Rake::Task.define_task :environment
  end

  describe "demo:test" do
    it "runs" do
      Rake::Task["demo:test"].invoke
    end
  end
end

You could define an instance variable in the before block to avoid the Rake::Task reference:

before :all do
  @rake = Rake::Application.new
  Rake.application = @rake
  Rake.application.rake_require 'lib/tasks/demo_tasks', [Rails.root.to_s]
  Rake::Task.define_task :environment
end

describe "demo:test" do
  it "runs" do
    @rake["demo:test"].invoke

IMO, less desirable for a number of reasons. Here's a summary I agree with.