Just re-define var in each context in the form it is needed in. foo is evaluated lazily so it will use the appropriate version of var defined in each context when it is eventually called in the it block:
describe 'my spec' do
let(:foo) { bar(var) }
context 'when c is defined' do
let(:var) { { a: 1, b: 2, c: 3 } }
it 'returns some result with c' do
expect(foo).to eq('bar with c') # or whatever it returns
end
end
context 'when d is defined' do
let(:var) { { a: 1, b: 2, d: 4 } }
it 'returns some result with d' do
expect(foo).to eq('bar with d') # or whatever it returns
end
end
end
Edit: if you really want nested lets, then I'd say either go with Igor's answer, or if the base definition of var won't be tested in the specs, then put it in a separate let statement (if it is tested, then you'll get unavoidable repetition as per the final example):
describe 'my spec' do
let(:base_var) { { a: 1, b: 2 } }
let(:foo) { bar(var) }
context 'when c is defined in var' do
let(:var) { base_var.merge(c: 3) }
it 'returns some result with c' do
expect(foo).to eq('bar with c') # or whatever it returns
end
end
context 'when d is defined in var' do
let(:var) { base_var.merge(d: 4) }
it 'returns some result with d' do
expect(foo).to eq('bar with d') # or whatever it returns
end
end
context 'when no changes made from base_var to var' do
let(:var) { base_var }
it 'returns some result from just bar' do
expect(foo).to eq('just bar') # or whatever it returns
end
end
end