Repository: narwhals-dev/narwhals
Pull request #1719 (2025-01-04 15:24:21)

chore: remove some lazy tests
I'm removing lazy backends from testing for methods which for now we don't want to support for lazy backends. Else it's just a big chore to keep increasing the xfails for them for duckdb / pyspark / ...

<!--
# Thanks for contributing a pull request! 
## Please make sure you see our contribution guidelines: https://github.com/narwhals-dev/narwhals/blob/main/CONTRIBUTING.md
-->

## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [ ] ✨ Feature
- [ ] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [ ] 📝 Documentation
- [ ] ✅ Test
- [ ] 🐳 Other

## Related issues

- Related issue #\<issue number\>
- Closes #\<issue number\>

## Checklist

- [ ] Code follows style guide (ruff)
- [ ] Tests added
- [ ] Documented the changes

## If you have comments or can explain your changes, please do so below


MarcoGorelli: merging so that duckdb / pyspark can be a bit less painful 😅 

Patch:
diff --git a/docs/backcompat.md b/docs/backcompat.md
index 8902d7a0..55b927fd 100644
--- a/docs/backcompat.md
+++ b/docs/backcompat.md
@@ -96,12 +96,13 @@ Anything currently in `narwhals.stable.v1` will not be changed or removed in fut
 
 Here are exceptions to our backwards compatibility policy:
 
-- unambiguous bugs. If a function contains what is unambiguously a bug, then we'll fix it, without
+- Unambiguous bugs. If a function contains what is unambiguously a bug, then we'll fix it, without
   considering that to be a breaking change.
-- radical changes in backends. Suppose that Polars was to remove
+- Radical changes in backends. Suppose that Polars was to remove
   expressions, or pandas were to remove support for categorical data. At that point, we might
   need to rethink Narwhals. However, we expect such radical changes to be exceedingly unlikely.
-- we may consider making some type hints more precise.
+- We may consider making some type hints more precise.
+- Anything labelled "unstable".
 
 In general, decision are driven by use-cases, and we conduct a search of public GitHub repositories
 before making any change.
diff --git a/narwhals/_dask/dataframe.py b/narwhals/_dask/dataframe.py
index 65bf5eeb..6542253a 100644
--- a/narwhals/_dask/dataframe.py
+++ b/narwhals/_dask/dataframe.py
@@ -94,17 +94,6 @@ class DaskLazyFrame(CompliantLazyFrame):
         return self._native_frame.columns.tolist()  # type: ignore[no-any-return]
 
     def filter(self, *predicates: DaskExpr, **constraints: Any) -> Self:
-        if (
-            len(predicates) == 1
-            and isinstance(predicates[0], list)
-            and all(isinstance(x, bool) for x in predicates[0])
-            and not constraints
-        ):
-            msg = (
-                "`LazyFrame.filter` is not supported for Dask backend with boolean masks."
-            )
-            raise NotImplementedError(msg)
-
         plx = self.__narwhals_namespace__()
         expr = plx.all_horizontal(
             *chain(predicates, (plx.col(name) == v for name, v in constraints.items()))
diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py
index e4107c04..7f50dbf7 100644
--- a/narwhals/_dask/expr.py
+++ b/narwhals/_dask/expr.py
@@ -401,14 +401,6 @@ class DaskExpr(CompliantExpr["dask_expr.Series"]):
             returns_scalar=self._returns_scalar,
         )
 
-    def map_batches(
-        self: Self,
-        function: Callable[[Any], Any],
-        return_dtype: DType | None = None,
-    ) -> NoReturn:
-        msg = "`Expr.map_batches` is not implemented for Dask yet"
-        raise NotImplementedError(msg)
-
     def mean(self) -> Self:
         return self._from_call(
             lambda _input: _input.mean(),
@@ -879,10 +871,6 @@ class DaskExpr(CompliantExpr["dask_expr.Series"]):
             kwargs={**self._kwargs, "keys": keys},
         )
 
-    def mode(self: Self) -> Self:
-        msg = "`Expr.mode` is not supported for the Dask backend."
-        raise NotImplementedError(msg)
-
     @property
     def str(self: Self) -> DaskExprStringNamespace:
         return DaskExprStringNamespace(self)
@@ -919,116 +907,6 @@ class DaskExpr(CompliantExpr["dask_expr.Series"]):
             returns_scalar=self._returns_scalar,
         )
 
-    def rolling_sum(
-        self: Self,
-        window_size: int,
-        *,
-        min_periods: int | None,
-        center: bool,
-    ) -> Self:
-        def func(
-            _input: dask_expr.Series,
-            window_size: int,
-            min_periods: int | None,
-            center: bool,  # noqa: FBT001
-        ) -> dask_expr.Series:
-            return _input.rolling(
-                window=window_size, min_periods=min_periods, center=center
-            ).sum()
-
-        return self._from_call(
-            func,
-            "rolling_sum",
-            window_size=window_size,
-            min_periods=min_periods,
-            center=center,
-            returns_scalar=self._returns_scalar,
-        )
-
-    def rolling_mean(
-        self: Self,
-        window_size: int,
-        *,
-        min_periods: int | None,
-        center: bool,
-    ) -> Self:
-        def func(
-            _input: dask_expr.Series,
-            window_size: int,
-            min_periods: int | None,
-            center: bool,  # noqa: FBT001
-        ) -> dask_expr.Series:
-            return _input.rolling(
-                window=window_size, min_periods=min_periods, center=center
-            ).mean()
-
-        return self._from_call(
-            func,
-            "rolling_mean",
-            window_size=window_size,
-            min_periods=min_periods,
-            center=center,
-            returns_scalar=self._returns_scalar,
-        )
-
-    def rolling_var(
-        self: Self,
-        window_size: int,
-        *,
-        min_periods: int | None,
-        center: bool,
-        ddof: int,
-    ) -> Self:
-        def func(
-            _input: dask_expr.Series,
-            window_size: int,
-            min_periods: int | None,
-            center: bool,  # noqa: FBT001
-            ddof: int,
-        ) -> dask_expr.Series:
-            return _input.rolling(
-                window=window_size, min_periods=min_periods, center=center
-            ).var(ddof=ddof)
-
-        return self._from_call(
-            func,
-            "rolling_var",
-            window_size=window_size,
-            min_periods=min_periods,
-            center=center,
-            ddof=ddof,
-            returns_scalar=self._returns_scalar,
-        )
-
-    def rolling_std(
-        self: Self,
-        window_size: int,
-        *,
-        min_periods: int | None,
-        center: bool,
-        ddof: int,
-    ) -> Self:
-        def func(
-            _input: dask_expr.Series,
-            window_size: int,
-            min_periods: int | None,
-            center: bool,  # noqa: FBT001
-            ddof: int,
-        ) -> dask_expr.Series:
-            return _input.rolling(
-                window=window_size, min_periods=min_periods, center=center
-            ).std(ddof=ddof)
-
-        return self._from_call(
-            func,
-            "rolling_std",
-            window_size=window_size,
-            min_periods=min_periods,
-            center=center,
-            ddof=ddof,
-            returns_scalar=self._returns_scalar,
-        )
-
 
 class DaskExprStringNamespace:
     def __init__(self, expr: DaskExpr) -> None:
diff --git a/narwhals/_spark_like/dataframe.py b/narwhals/_spark_like/dataframe.py
index 00fac84d..f4a779c2 100644
--- a/narwhals/_spark_like/dataframe.py
+++ b/narwhals/_spark_like/dataframe.py
@@ -105,13 +105,6 @@ class SparkLikeLazyFrame:
         return self._from_native_frame(self._native_frame.select(*new_columns_list))
 
     def filter(self, *predicates: SparkLikeExpr) -> Self:
-        if (
-            len(predicates) == 1
-            and isinstance(predicates[0], list)
-            and all(isinstance(x, bool) for x in predicates[0])
-        ):
-            msg = "`LazyFrame.filter` is not supported for PySpark backend with boolean masks."
-            raise NotImplementedError(msg)
         plx = self.__narwhals_namespace__()
         expr = plx.all_horizontal(*predicates)
         # `[0]` is safe as all_horizontal's expression only returns a single column
diff --git a/narwhals/dataframe.py b/narwhals/dataframe.py
index 7a5d5b4d..fcf8648d 100644
--- a/narwhals/dataframe.py
+++ b/narwhals/dataframe.py
@@ -4597,6 +4597,15 @@ class LazyFrame(BaseFrame[FrameT]):
                foo  bar ham
             1    2    7   b
         """
+        if (
+            len(predicates) == 1
+            and isinstance(predicates[0], list)
+            and all(isinstance(x, bool) for x in predicates[0])
+            and not constraints
+        ):  # pragma: no cover
+            msg = "`LazyFrame.filter` is not supported with Python boolean masks - use expressions instead."
+            raise TypeError(msg)
+
         return super().filter(*predicates, **constraints)
 
     def group_by(
@@ -5029,6 +5038,7 @@ class LazyFrame(BaseFrame[FrameT]):
             ...     return (
             ...         df.sort("datetime")
             ...         .join_asof(other, on="datetime", by="ticker")
+            ...         .sort("datetime", "ticker")
             ...         .collect()
             ...         .to_native()
             ...     )
@@ -5044,16 +5054,16 @@ class LazyFrame(BaseFrame[FrameT]):
             ╞════════════════════════════╪════════╪════════╪══════════╪═══════╪════════╡
             │ 2016-05-25 13:30:00.000023 ┆ MSFT   ┆ 51.95  ┆ 75       ┆ 51.95 ┆ 51.96  │
             │ 2016-05-25 13:30:00.000038 ┆ MSFT   ┆ 51.95  ┆ 155      ┆ 51.97 ┆ 51.98  │
+            │ 2016-05-25 13:30:00.000048 ┆ AAPL   ┆ 98.0   ┆ 100      ┆ null  ┆ null   │
             │ 2016-05-25 13:30:00.000048 ┆ GOOG   ┆ 720.77 ┆ 100      ┆ 720.5 ┆ 720.93 │
             │ 2016-05-25 13:30:00.000048 ┆ GOOG   ┆ 720.92 ┆ 100      ┆ 720.5 ┆ 720.93 │
-            │ 2016-05-25 13:30:00.000048 ┆ AAPL   ┆ 98.0   ┆ 100      ┆ null  ┆ null   │
             └────────────────────────────┴────────┴────────┴──────────┴───────┴────────┘
             >>> agnostic_join_asof_datetime_by_ticker(trades_dask, quotes_dask)
                                 datetime ticker   price  quantity     bid     ask
             0 2016-05-25 13:30:00.000023   MSFT   51.95        75   51.95   51.96
             0 2016-05-25 13:30:00.000038   MSFT   51.95       155   51.97   51.98
-            1 2016-05-25 13:30:00.000048   GOOG  720.92       100  720.50  720.93
             2 2016-05-25 13:30:00.000048   AAPL   98.00       100     NaN     NaN
+            1 2016-05-25 13:30:00.000048   GOOG  720.92       100  720.50  720.93
             3 2016-05-25 13:30:00.000048   GOOG  720.77       100  720.50  720.93
         """
         return super().join_asof(


Test patch:
diff --git a/tests/expr_and_series/arg_max_test.py b/tests/expr_and_series/arg_max_test.py
index c8065c86..45b1ad06 100644
--- a/tests/expr_and_series/arg_max_test.py
+++ b/tests/expr_and_series/arg_max_test.py
@@ -3,7 +3,6 @@ from __future__ import annotations
 import pytest
 
 import narwhals.stable.v1 as nw
-from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
@@ -11,18 +10,15 @@ data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9], "i": [3, 1, 5]}
 
 
 def test_expr_arg_max_expr(
-    constructor: Constructor, request: pytest.FixtureRequest
+    constructor_eager: ConstructorEager, request: pytest.FixtureRequest
 ) -> None:
-    if "dask" in str(constructor):
-        # This operation is row-order dependent so we don't support it for Dask
-        request.applymarker(pytest.mark.xfail)
-    if "modin" in str(constructor):
+    if "modin" in str(constructor_eager):
         # TODO(unassigned): bug in modin?
         return
-    if "cudf" in str(constructor):
+    if "cudf" in str(constructor_eager):
         # not implemented yet
         request.applymarker(pytest.mark.xfail)
-    df = nw.from_native(constructor(data))
+    df = nw.from_native(constructor_eager(data))
     df = nw.maybe_set_index(df, "i")
     result = df.select(nw.col("a", "b", "z").arg_max())
     expected = {"a": [1], "b": [2], "z": [2]}
diff --git a/tests/expr_and_series/arg_min_test.py b/tests/expr_and_series/arg_min_test.py
index 99add1b8..1654f6fe 100644
--- a/tests/expr_and_series/arg_min_test.py
+++ b/tests/expr_and_series/arg_min_test.py
@@ -3,7 +3,6 @@ from __future__ import annotations
 import pytest
 
 import narwhals.stable.v1 as nw
-from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
@@ -11,15 +10,12 @@ data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]}
 
 
 def test_expr_arg_min_expr(
-    constructor: Constructor, request: pytest.FixtureRequest
+    constructor_eager: ConstructorEager, request: pytest.FixtureRequest
 ) -> None:
-    if "dask" in str(constructor):
-        # This operation is row-order dependent so we don't support it for Dask
-        request.applymarker(pytest.mark.xfail)
-    if "cudf" in str(constructor):
+    if "cudf" in str(constructor_eager):
         # not implemented yet
         request.applymarker(pytest.mark.xfail)
-    df = nw.from_native(constructor(data))
+    df = nw.from_native(constructor_eager(data))
     result = df.select(nw.col("a", "b", "z").arg_min())
     expected = {"a": [0], "b": [0], "z": [0]}
     assert_equal_data(result, expected)
diff --git a/tests/expr_and_series/arg_true_test.py b/tests/expr_and_series/arg_true_test.py
index 7dfeaa46..6db10dab 100644
--- a/tests/expr_and_series/arg_true_test.py
+++ b/tests/expr_and_series/arg_true_test.py
@@ -3,15 +3,16 @@ from __future__ import annotations
 import pytest
 
 import narwhals.stable.v1 as nw
-from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
 
-def test_arg_true(constructor: Constructor, request: pytest.FixtureRequest) -> None:
-    if "dask" in str(constructor):
+def test_arg_true(
+    constructor_eager: ConstructorEager, request: pytest.FixtureRequest
+) -> None:
+    if "dask" in str(constructor_eager):
         request.applymarker(pytest.mark.xfail)
-    df = nw.from_native(constructor({"a": [1, None, None, 3]}))
+    df = nw.from_native(constructor_eager({"a": [1, None, None, 3]}))
     result = df.select(nw.col("a").is_null().arg_true())
     expected = {"a": [1, 2]}
     assert_equal_data(result, expected)
diff --git a/tests/expr_and_series/drop_nulls_test.py b/tests/expr_and_series/drop_nulls_test.py
index 70baf1f8..0584674e 100644
--- a/tests/expr_and_series/drop_nulls_test.py
+++ b/tests/expr_and_series/drop_nulls_test.py
@@ -1,16 +1,11 @@
 from __future__ import annotations
 
-import pytest
-
 import narwhals.stable.v1 as nw
-from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
 
-def test_drop_nulls(constructor: Constructor, request: pytest.FixtureRequest) -> None:
-    if "dask" in str(constructor):
-        request.applymarker(pytest.mark.xfail)
+def test_drop_nulls(constructor_eager: ConstructorEager) -> None:
     data = {
         "A": [1, 2, None, 4],
         "B": [5, 6, 7, 8],
@@ -18,7 +13,7 @@ def test_drop_nulls(constructor: Constructor, request: pytest.FixtureRequest) ->
         "D": [9, 10, 11, 12],
     }
 
-    df = nw.from_native(constructor(data))
+    df = nw.from_native(constructor_eager(data))
 
     result_a = df.select(nw.col("A").drop_nulls())
     result_b = df.select(nw.col("B").drop_nulls())
diff --git a/tests/expr_and_series/ewm_test.py b/tests/expr_and_series/ewm_test.py
index f2fd8727..2463809e 100644
--- a/tests/expr_and_series/ewm_test.py
+++ b/tests/expr_and_series/ewm_test.py
@@ -1,10 +1,8 @@
 from __future__ import annotations
 
-import pandas as pd
 import pytest
 
 import narwhals.stable.v1 as nw
-from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
@@ -14,11 +12,13 @@ data = {"a": [1, 1, 2], "b": [1, 2, 3]}
 @pytest.mark.filterwarnings(
     "ignore:`Expr.ewm_mean` is being called from the stable API although considered an unstable feature."
 )
-def test_ewm_mean_expr(request: pytest.FixtureRequest, constructor: Constructor) -> None:
-    if any(x in str(constructor) for x in ("pyarrow_table_", "dask", "modin")):
+def test_ewm_mean_expr(
+    request: pytest.FixtureRequest, constructor_eager: ConstructorEager
+) -> None:
+    if any(x in str(constructor_eager) for x in ("pyarrow_table_", "modin")):
         request.applymarker(pytest.mark.xfail)
 
-    df = nw.from_native(constructor(data))
+    df = nw.from_native(constructor_eager(data))
     result = df.select(nw.col("a", "b").ewm_mean(com=1))
     expected = {
         "a": [1.0, 1.0, 1.5714285714285714],
@@ -66,34 +66,18 @@ def test_ewm_mean_series(
 )
 def test_ewm_mean_expr_adjust(
     request: pytest.FixtureRequest,
-    constructor: Constructor,
+    constructor_eager: ConstructorEager,
     adjust: bool,  # noqa: FBT001
     expected: dict[str, list[float]],
 ) -> None:
-    if any(x in str(constructor) for x in ("pyarrow_table_", "dask", "modin")):
+    if any(x in str(constructor_eager) for x in ("pyarrow_table_", "modin")):
         request.applymarker(pytest.mark.xfail)
 
-    df = nw.from_native(constructor(data))
+    df = nw.from_native(constructor_eager(data))
     result = df.select(nw.col("a", "b").ewm_mean(com=1, adjust=adjust))
     assert_equal_data(result, expected)
 
 
-@pytest.mark.filterwarnings(
-    "ignore:`Expr.ewm_mean` is being called from the stable API although considered an unstable feature."
-)
-def test_ewm_mean_dask_raise() -> None:
-    pytest.importorskip("dask")
-    pytest.importorskip("dask_expr", exc_type=ImportError)
-    import dask.dataframe as dd
-
-    df = nw.from_native(dd.from_pandas(pd.DataFrame({"a": [1, 2, 3]})))
-    with pytest.raises(
-        NotImplementedError,
-        match="`Expr.ewm_mean` is not supported for the Dask backend",
-    ):
-        df.select(nw.col("a").ewm_mean(com=1))
-
-
 @pytest.mark.filterwarnings(
     "ignore:`Expr.ewm_mean` is being called from the stable API although considered an unstable feature."
 )
@@ -128,12 +112,12 @@ def test_ewm_mean_nulls(
     request: pytest.FixtureRequest,
     ignore_nulls: bool,  # noqa: FBT001
     expected: dict[str, list[float]],
-    constructor: Constructor,
+    constructor_eager: ConstructorEager,
 ) -> None:
-    if any(x in str(constructor) for x in ("pyarrow_table_", "dask", "modin", "cudf")):
+    if any(x in str(constructor_eager) for x in ("pyarrow_table_", "modin", "cudf")):
         request.applymarker(pytest.mark.xfail)
 
-    df = nw.from_native(constructor({"a": [2.0, 4.0, None, 3.0]}))
+    df = nw.from_native(constructor_eager({"a": [2.0, 4.0, None, 3.0]}))
     result = df.select(nw.col("a").ewm_mean(com=1, ignore_nulls=ignore_nulls))
     assert_equal_data(result, expected)
 
@@ -143,12 +127,12 @@ def test_ewm_mean_nulls(
 )
 def test_ewm_mean_params(
     request: pytest.FixtureRequest,
-    constructor: Constructor,
+    constructor_eager: ConstructorEager,
 ) -> None:
-    if any(x in str(constructor) for x in ("pyarrow_table_", "dask", "modin", "cudf")):
+    if any(x in str(constructor_eager) for x in ("pyarrow_table_", "modin", "cudf")):
         request.applymarker(pytest.mark.xfail)
 
-    df = nw.from_native(constructor({"a": [2, 5, 3]}))
+    df = nw.from_native(constructor_eager({"a": [2, 5, 3]}))
     expected: dict[str, list[float | None]] = {"a": [2.0, 4.0, 3.4285714285714284]}
     assert_equal_data(
         df.select(nw.col("a").ewm_mean(alpha=0.5, adjust=True, ignore_nulls=True)),
diff --git a/tests/expr_and_series/filter_test.py b/tests/expr_and_series/filter_test.py
index 1d2b8b96..5c0d95d3 100644
--- a/tests/expr_and_series/filter_test.py
+++ b/tests/expr_and_series/filter_test.py
@@ -1,7 +1,5 @@
 from __future__ import annotations
 
-import pytest
-
 import narwhals.stable.v1 as nw
 from tests.utils import Constructor
 from tests.utils import ConstructorEager
@@ -15,10 +13,8 @@ data = {
 }
 
 
-def test_filter(constructor: Constructor, request: pytest.FixtureRequest) -> None:
-    if "dask" in str(constructor):
-        request.applymarker(pytest.mark.xfail)
-    df = nw.from_native(constructor(data))
+def test_filter(constructor_eager: ConstructorEager) -> None:
+    df = nw.from_native(constructor_eager(data))
     result = df.select(nw.col("a").filter(nw.col("i") < 2, nw.col("c") == 5))
     expected = {"a": [0]}
     assert_equal_data(result, expected)
diff --git a/tests/expr_and_series/gather_every_test.py b/tests/expr_and_series/gather_every_test.py
index 7ec7a62c..fdaaac08 100644
--- a/tests/expr_and_series/gather_every_test.py
+++ b/tests/expr_and_series/gather_every_test.py
@@ -3,7 +3,6 @@ from __future__ import annotations
 import pytest
 
 import narwhals.stable.v1 as nw
-from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
@@ -13,11 +12,9 @@ data = {"a": list(range(10))}
 @pytest.mark.parametrize("n", [1, 2, 3])
 @pytest.mark.parametrize("offset", [1, 2, 3])
 def test_gather_every_expr(
-    constructor: Constructor, n: int, offset: int, request: pytest.FixtureRequest
+    constructor_eager: ConstructorEager, n: int, offset: int
 ) -> None:
-    if "dask" in str(constructor):
-        request.applymarker(pytest.mark.xfail)
-    df = nw.from_native(constructor(data))
+    df = nw.from_native(constructor_eager(data))
 
     result = df.select(nw.col("a").gather_every(n=n, offset=offset))
     expected = {"a": data["a"][offset::n]}
diff --git a/tests/expr_and_series/head_test.py b/tests/expr_and_series/head_test.py
index 499114f0..49cc4124 100644
--- a/tests/expr_and_series/head_test.py
+++ b/tests/expr_and_series/head_test.py
@@ -3,18 +3,17 @@ from __future__ import annotations
 import pytest
 
 import narwhals as nw
-from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
 
 @pytest.mark.parametrize("n", [2, -1])
-def test_head(constructor: Constructor, n: int, request: pytest.FixtureRequest) -> None:
-    if "dask" in str(constructor):
+def test_head(
+    constructor_eager: ConstructorEager, n: int, request: pytest.FixtureRequest
+) -> None:
+    if "polars" in str(constructor_eager) and n < 0:
         request.applymarker(pytest.mark.xfail)
-    if "polars" in str(constructor) and n < 0:
-        request.applymarker(pytest.mark.xfail)
-    df = nw.from_native(constructor({"a": [1, 2, 3]}))
+    df = nw.from_native(constructor_eager({"a": [1, 2, 3]}))
     result = df.select(nw.col("a").head(n))
     expected = {"a": [1, 2]}
     assert_equal_data(result, expected)
diff --git a/tests/expr_and_series/len_test.py b/tests/expr_and_series/len_test.py
index 55a46f64..fffcbd4a 100644
--- a/tests/expr_and_series/len_test.py
+++ b/tests/expr_and_series/len_test.py
@@ -19,12 +19,14 @@ def test_len_no_filter(constructor: Constructor) -> None:
     assert_equal_data(df, expected)
 
 
-def test_len_chaining(constructor: Constructor, request: pytest.FixtureRequest) -> None:
+def test_len_chaining(
+    constructor_eager: ConstructorEager, request: pytest.FixtureRequest
+) -> None:
     data = {"a": list("xyz"), "b": [1, 2, 1]}
     expected = {"a1": [2], "a2": [1]}
-    if "dask" in str(constructor):
+    if "dask" in str(constructor_eager):
         request.applymarker(pytest.mark.xfail)
-    df = nw.from_native(constructor(data)).select(
+    df = nw.from_native(constructor_eager(data)).select(
         nw.col("a").filter(nw.col("b") == 1).len().alias("a1"),
         nw.col("a").filter(nw.col("b") == 2).len().alias("a2"),
     )
diff --git a/tests/expr_and_series/map_batches_test.py b/tests/expr_and_series/map_batches_test.py
index f2720e20..7d4f8f41 100644
--- a/tests/expr_and_series/map_batches_test.py
+++ b/tests/expr_and_series/map_batches_test.py
@@ -1,36 +1,28 @@
 from __future__ import annotations
 
-import pandas as pd
 import pytest
 
 import narwhals.stable.v1 as nw
 from tests.utils import POLARS_VERSION
-from tests.utils import Constructor
+from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
 data = {"a": [1, 2, 3], "b": [4, 5, 6], "z": [7.0, 8.0, 9.0]}
 
 
-def test_map_batches_expr(
-    request: pytest.FixtureRequest, constructor: Constructor
-) -> None:
-    if "dask" in str(constructor):
-        request.applymarker(pytest.mark.xfail)
-
-    df = nw.from_native(constructor(data))
+def test_map_batches_expr(constructor_eager: ConstructorEager) -> None:
+    df = nw.from_native(constructor_eager(data))
     expected = df.select(nw.col("a", "b").map_batches(lambda s: s + 1))
     assert_equal_data(expected, {"a": [2, 3, 4], "b": [5, 6, 7]})
 
 
 def test_map_batches_expr_numpy(
-    request: pytest.FixtureRequest, constructor: Constructor
+    request: pytest.FixtureRequest, constructor_eager: ConstructorEager
 ) -> None:
-    if "dask" in str(constructor) or (
-        "polars" in str(constructor) and POLARS_VERSION <= (0, 20, 3)
-    ):
+    if "polars" in str(constructor_eager) and POLARS_VERSION <= (0, 20, 3):
         request.applymarker(pytest.mark.xfail)
 
-    df = nw.from_native(constructor(data))
+    df = nw.from_native(constructor_eager(data))
     expected = df.select(
         nw.col("a")
         .map_batches(lambda s: s.to_numpy() + 1, return_dtype=nw.Float64())
@@ -43,25 +35,11 @@ def test_map_batches_expr_numpy(
 
 
 def test_map_batches_expr_names(
-    request: pytest.FixtureRequest, constructor: Constructor
+    request: pytest.FixtureRequest, constructor_eager: ConstructorEager
 ) -> None:
-    if "dask" in str(constructor) or (
-        "polars" in str(constructor) and POLARS_VERSION <= (0, 20, 3)
-    ):
+    if "polars" in str(constructor_eager) and POLARS_VERSION <= (0, 20, 3):
         request.applymarker(pytest.mark.xfail)
 
-    df = nw.from_native(constructor(data))
+    df = nw.from_native(constructor_eager(data))
     expected = nw.from_native(df.select(nw.all().map_batches(lambda x: x.to_numpy())))
     assert_equal_data(expected, {"a": [1, 2, 3], "b": [4, 5, 6], "z": [7.0, 8.0, 9.0]})
-
-
-def test_map_batches_raise() -> None:
-    pytest.importorskip("dask")
-    pytest.importorskip("dask_expr", exc_type=ImportError)
-    import dask.dataframe as dd
-
-    df = nw.from_native(dd.from_pandas(pd.DataFrame({"a": [1, 2, 3]})))
-    with pytest.raises(
-        NotImplementedError, match="`Expr.map_batches` is not implemented for Dask yet"
-    ):
-        df.select(nw.col("a").map_batches(lambda x: x.to_numpy()))
diff --git a/tests/expr_and_series/mode_test.py b/tests/expr_and_series/mode_test.py
index 9b94ecaf..a78a442f 100644
--- a/tests/expr_and_series/mode_test.py
+++ b/tests/expr_and_series/mode_test.py
@@ -14,27 +14,20 @@ data = {
 }
 
 
-def test_mode_single_expr(
-    constructor: Constructor, request: pytest.FixtureRequest
-) -> None:
-    if "dask" in str(constructor):
-        request.applymarker(pytest.mark.xfail)
-
-    df = nw.from_native(constructor(data))
+def test_mode_single_expr(constructor_eager: Constructor) -> None:
+    df = nw.from_native(constructor_eager(data))
     result = df.select(nw.col("a").mode()).sort("a")
     expected = {"a": [1, 2]}
     assert_equal_data(result, expected)
 
 
 def test_mode_multi_expr(
-    constructor: Constructor,
+    constructor_eager: ConstructorEager,
     request: pytest.FixtureRequest,
 ) -> None:
-    if "dask" in str(constructor) or (
-        "polars" in str(constructor) and POLARS_VERSION >= (1, 7, 0)
-    ):
+    if "polars" in str(constructor_eager) and POLARS_VERSION >= (1, 7, 0):
         request.applymarker(pytest.mark.xfail)
-    df = nw.from_native(constructor(data))
+    df = nw.from_native(constructor_eager(data))
     result = df.select(nw.col("a", "b").mode()).sort("a", "b")
     expected = {"a": [1, 2], "b": [3, 3]}
     assert_equal_data(result, expected)
diff --git a/tests/expr_and_series/rolling_mean_test.py b/tests/expr_and_series/rolling_mean_test.py
index a6dd4193..2fb6a47f 100644
--- a/tests/expr_and_series/rolling_mean_test.py
+++ b/tests/expr_and_series/rolling_mean_test.py
@@ -11,7 +11,6 @@ from hypothesis import given
 
 import narwhals.stable.v1 as nw
 from tests.utils import PANDAS_VERSION
-from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
@@ -41,16 +40,8 @@ kwargs_and_expected: dict[str, dict[str, Any]] = {
 @pytest.mark.filterwarnings(
     "ignore:`Expr.rolling_mean` is being called from the stable API although considered an unstable feature."
 )
-def test_rolling_mean_expr(
-    request: pytest.FixtureRequest, constructor: Constructor
-) -> None:
-    if "dask" in str(constructor):
-        # TODO(FBruzzesi): Dask is raising the following error:
-        # NotImplementedError: Partition size is less than overlapping window size.
-        # Try using ``df.repartition`` to increase the partition size.
-        request.applymarker(pytest.mark.xfail)
-
-    df = nw.from_native(constructor(data))
+def test_rolling_mean_expr(constructor_eager: ConstructorEager) -> None:
+    df = nw.from_native(constructor_eager(data))
     result = df.select(
         **{
             name: nw.col("a").rolling_mean(**values["kwargs"])
diff --git a/tests/expr_and_series/rolling_std_test.py b/tests/expr_and_series/rolling_std_test.py
index b937f843..1b9f4998 100644
--- a/tests/expr_and_series/rolling_std_test.py
+++ b/tests/expr_and_series/rolling_std_test.py
@@ -7,7 +7,6 @@ import pytest
 
 import narwhals.stable.v1 as nw
 from tests.utils import POLARS_VERSION
-from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
@@ -70,22 +69,17 @@ kwargs_and_expected = (
 @pytest.mark.parametrize("kwargs_and_expected", kwargs_and_expected)
 def test_rolling_std_expr(
     request: pytest.FixtureRequest,
-    constructor: Constructor,
+    constructor_eager: ConstructorEager,
     kwargs_and_expected: dict[str, Any],
 ) -> None:
     name = kwargs_and_expected["name"]
     kwargs = kwargs_and_expected["kwargs"]
     expected = kwargs_and_expected["expected"]
 
-    if "dask" in str(constructor) or (
-        "polars" in str(constructor) and POLARS_VERSION < (1,)
-    ):
-        # TODO(FBruzzesi): Dask is raising the following error:
-        # NotImplementedError: Partition size is less than overlapping window size.
-        # Try using ``df.repartition`` to increase the partition size.
+    if "polars" in str(constructor_eager) and POLARS_VERSION < (1,):
         request.applymarker(pytest.mark.xfail)
 
-    df = nw.from_native(constructor(data))
+    df = nw.from_native(constructor_eager(data))
     result = df.select(nw.col("a").rolling_std(**kwargs).alias(name))
 
     assert_equal_data(result, {name: expected})
diff --git a/tests/expr_and_series/rolling_sum_test.py b/tests/expr_and_series/rolling_sum_test.py
index 8c4537e4..0e395195 100644
--- a/tests/expr_and_series/rolling_sum_test.py
+++ b/tests/expr_and_series/rolling_sum_test.py
@@ -12,7 +12,6 @@ from hypothesis import given
 import narwhals.stable.v1 as nw
 from narwhals.exceptions import InvalidOperationError
 from tests.utils import PANDAS_VERSION
-from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
@@ -42,16 +41,8 @@ kwargs_and_expected: dict[str, dict[str, Any]] = {
 @pytest.mark.filterwarnings(
     "ignore:`Expr.rolling_sum` is being called from the stable API although considered an unstable feature."
 )
-def test_rolling_sum_expr(
-    request: pytest.FixtureRequest, constructor: Constructor
-) -> None:
-    if "dask" in str(constructor):
-        # TODO(FBruzzesi): Dask is raising the following error:
-        # NotImplementedError: Partition size is less than overlapping window size.
-        # Try using ``df.repartition`` to increase the partition size.
-        request.applymarker(pytest.mark.xfail)
-
-    df = nw.from_native(constructor(data))
+def test_rolling_sum_expr(constructor_eager: ConstructorEager) -> None:
+    df = nw.from_native(constructor_eager(data))
     result = df.select(
         **{
             name: nw.col("a").rolling_sum(**values["kwargs"])
@@ -126,9 +117,12 @@ def test_rolling_sum_series(constructor_eager: ConstructorEager) -> None:
     ],
 )
 def test_rolling_sum_expr_invalid_params(
-    constructor: Constructor, window_size: int, min_periods: int | None, context: Any
+    constructor_eager: ConstructorEager,
+    window_size: int,
+    min_periods: int | None,
+    context: Any,
 ) -> None:
-    df = nw.from_native(constructor(data))
+    df = nw.from_native(constructor_eager(data))
 
     with context:
         df.select(
diff --git a/tests/expr_and_series/rolling_var_test.py b/tests/expr_and_series/rolling_var_test.py
index 37475e76..4d4c596d 100644
--- a/tests/expr_and_series/rolling_var_test.py
+++ b/tests/expr_and_series/rolling_var_test.py
@@ -13,7 +13,6 @@ from hypothesis import given
 import narwhals.stable.v1 as nw
 from tests.utils import PANDAS_VERSION
 from tests.utils import POLARS_VERSION
-from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
@@ -59,22 +58,20 @@ kwargs_and_expected = (
 @pytest.mark.parametrize("kwargs_and_expected", kwargs_and_expected)
 def test_rolling_var_expr(
     request: pytest.FixtureRequest,
-    constructor: Constructor,
+    constructor_eager: ConstructorEager,
     kwargs_and_expected: dict[str, Any],
 ) -> None:
     name = kwargs_and_expected["name"]
     kwargs = kwargs_and_expected["kwargs"]
     expected = kwargs_and_expected["expected"]
 
-    if "dask" in str(constructor) or (
-        "polars" in str(constructor) and POLARS_VERSION < (1,)
-    ):
+    if "polars" in str(constructor_eager) and POLARS_VERSION < (1,):
         # TODO(FBruzzesi): Dask is raising the following error:
         # NotImplementedError: Partition size is less than overlapping window size.
         # Try using ``df.repartition`` to increase the partition size.
         request.applymarker(pytest.mark.xfail)
 
-    df = nw.from_native(constructor(data))
+    df = nw.from_native(constructor_eager(data))
     result = df.select(nw.col("a").rolling_var(**kwargs).alias(name))
 
     assert_equal_data(result, {name: expected})
diff --git a/tests/expr_and_series/sample_test.py b/tests/expr_and_series/sample_test.py
index 009acc3c..8e88ba7c 100644
--- a/tests/expr_and_series/sample_test.py
+++ b/tests/expr_and_series/sample_test.py
@@ -3,14 +3,12 @@ from __future__ import annotations
 import pytest
 
 import narwhals.stable.v1 as nw
-from tests.utils import Constructor
+from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
 
-def test_expr_sample(constructor: Constructor, request: pytest.FixtureRequest) -> None:
-    if "dask" in str(constructor):
-        request.applymarker(pytest.mark.xfail)
-    df = nw.from_native(constructor({"a": [1, 2, 3], "b": [4, 5, 6]})).lazy()
+def test_expr_sample(constructor_eager: ConstructorEager) -> None:
+    df = nw.from_native(constructor_eager({"a": [1, 2, 3], "b": [4, 5, 6]})).lazy()
 
     result_expr = df.select(nw.col("a").sample(n=2)).collect().shape
     expected_expr = (2, 1)
@@ -22,11 +20,13 @@ def test_expr_sample(constructor: Constructor, request: pytest.FixtureRequest) -
 
 
 def test_expr_sample_fraction(
-    constructor: Constructor, request: pytest.FixtureRequest
+    constructor_eager: ConstructorEager, request: pytest.FixtureRequest
 ) -> None:
-    if "dask" in str(constructor):
+    if "dask" in str(constructor_eager):
         request.applymarker(pytest.mark.xfail)
-    df = nw.from_native(constructor({"a": [1, 2, 3] * 10, "b": [4, 5, 6] * 10})).lazy()
+    df = nw.from_native(
+        constructor_eager({"a": [1, 2, 3] * 10, "b": [4, 5, 6] * 10})
+    ).lazy()
 
     result_expr = df.select(nw.col("a").sample(fraction=0.1)).collect().shape
     expected_expr = (3, 1)
@@ -37,14 +37,9 @@ def test_expr_sample_fraction(
     assert result_series == expected_series
 
 
-def test_sample_with_seed(
-    constructor: Constructor, request: pytest.FixtureRequest
-) -> None:
-    if "dask" in str(constructor):
-        request.applymarker(pytest.mark.xfail)
-
+def test_sample_with_seed(constructor_eager: ConstructorEager) -> None:
     size, n = 100, 10
-    df = nw.from_native(constructor({"a": list(range(size))})).lazy()
+    df = nw.from_native(constructor_eager({"a": list(range(size))})).lazy()
     expected = {"res1": [True], "res2": [False]}
     result = df.select(
         seed1=nw.col("a").sample(n=n, seed=123),
diff --git a/tests/expr_and_series/tail_test.py b/tests/expr_and_series/tail_test.py
index a9ba5b20..e0481381 100644
--- a/tests/expr_and_series/tail_test.py
+++ b/tests/expr_and_series/tail_test.py
@@ -3,18 +3,17 @@ from __future__ import annotations
 import pytest
 
 import narwhals as nw
-from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
 
 @pytest.mark.parametrize("n", [2, -1])
-def test_head(constructor: Constructor, n: int, request: pytest.FixtureRequest) -> None:
-    if "dask" in str(constructor):
+def test_head(
+    constructor_eager: ConstructorEager, n: int, request: pytest.FixtureRequest
+) -> None:
+    if "polars" in str(constructor_eager) and n < 0:
         request.applymarker(pytest.mark.xfail)
-    if "polars" in str(constructor) and n < 0:
-        request.applymarker(pytest.mark.xfail)
-    df = nw.from_native(constructor({"a": [1, 2, 3]}))
+    df = nw.from_native(constructor_eager({"a": [1, 2, 3]}))
     result = df.select(nw.col("a").tail(n))
     expected = {"a": [2, 3]}
     assert_equal_data(result, expected)
diff --git a/tests/expr_and_series/unique_test.py b/tests/expr_and_series/unique_test.py
index 62d2e63a..989a577c 100644
--- a/tests/expr_and_series/unique_test.py
+++ b/tests/expr_and_series/unique_test.py
@@ -1,9 +1,6 @@
 from __future__ import annotations
 
-import pytest
-
 import narwhals.stable.v1 as nw
-from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
@@ -11,10 +8,8 @@ data = {"a": [1, 1, 2]}
 data_str = {"a": ["x", "x", "y"]}
 
 
-def test_unique_expr(constructor: Constructor, request: pytest.FixtureRequest) -> None:
-    if "dask" in str(constructor):
-        request.applymarker(pytest.mark.xfail)
-    df = nw.from_native(constructor(data))
+def test_unique_expr(constructor_eager: ConstructorEager) -> None:
+    df = nw.from_native(constructor_eager(data))
     result = df.select(nw.col("a").unique())
     expected = {"a": [1, 2]}
     assert_equal_data(result, expected)
diff --git a/tests/expr_and_series/when_test.py b/tests/expr_and_series/when_test.py
index 8648ae4f..5c60febb 100644
--- a/tests/expr_and_series/when_test.py
+++ b/tests/expr_and_series/when_test.py
@@ -52,13 +52,8 @@ def test_no_arg_when_fail(constructor: Constructor) -> None:
         df.select(nw.when().then(value=3).alias("a_when"))
 
 
-def test_value_numpy_array(
-    request: pytest.FixtureRequest, constructor: Constructor
-) -> None:
-    if "dask" in str(constructor):
-        request.applymarker(pytest.mark.xfail)
-
-    df = nw.from_native(constructor(data))
+def test_value_numpy_array(constructor_eager: ConstructorEager) -> None:
+    df = nw.from_native(constructor_eager(data))
     import numpy as np
 
     result = df.select(
@@ -91,13 +86,8 @@ def test_value_expression(constructor: Constructor) -> None:
     assert_equal_data(result, expected)
 
 
-def test_otherwise_numpy_array(
-    request: pytest.FixtureRequest, constructor: Constructor
-) -> None:
-    if "dask" in str(constructor):
-        request.applymarker(pytest.mark.xfail)
-
-    df = nw.from_native(constructor(data))
+def test_otherwise_numpy_array(constructor_eager: ConstructorEager) -> None:
+    df = nw.from_native(constructor_eager(data))
 
     result = df.select(
         nw.when(nw.col("a") == 1).then(-1).otherwise(np.array([0, 9, 10])).alias("a_when")
diff --git a/tests/frame/filter_test.py b/tests/frame/filter_test.py
index 8721f3bd..b55ab776 100644
--- a/tests/frame/filter_test.py
+++ b/tests/frame/filter_test.py
@@ -20,16 +20,11 @@ def test_filter(constructor: Constructor) -> None:
 def test_filter_with_boolean_list(constructor: Constructor) -> None:
     data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]}
     df = nw.from_native(constructor(data))
-
     context = (
-        pytest.raises(
-            NotImplementedError,
-            match="`LazyFrame.filter` is not supported for Dask backend with boolean masks.",
-        )
-        if "dask" in str(constructor)
+        pytest.raises(TypeError, match="not supported")
+        if isinstance(df, nw.LazyFrame)
         else does_not_raise()
     )
-
     with context:
         result = df.filter([False, True, True])
         expected = {"a": [3, 2], "b": [4, 6], "z": [8.0, 9.0]}
diff --git a/tests/frame/with_columns_sequence_test.py b/tests/frame/with_columns_sequence_test.py
index b88036a4..11c6cb49 100644
--- a/tests/frame/with_columns_sequence_test.py
+++ b/tests/frame/with_columns_sequence_test.py
@@ -1,10 +1,9 @@
 from __future__ import annotations
 
 import numpy as np
-import pytest
 
 import narwhals.stable.v1 as nw
-from tests.utils import Constructor
+from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
 data = {
@@ -13,11 +12,9 @@ data = {
 }
 
 
-def test_with_columns(constructor: Constructor, request: pytest.FixtureRequest) -> None:
-    if "dask" in str(constructor):
-        request.applymarker(pytest.mark.xfail)
+def test_with_columns(constructor_eager: ConstructorEager) -> None:
     result = (
-        nw.from_native(constructor(data))
+        nw.from_native(constructor_eager(data))
         .with_columns(d=np.array([4, 5]))
         .with_columns(e=nw.col("d") + 1)
         .select("d", "e")
diff --git a/tests/spark_like_test.py b/tests/spark_like_test.py
index b597ecd6..ffb841b4 100644
--- a/tests/spark_like_test.py
+++ b/tests/spark_like_test.py
@@ -115,18 +115,6 @@ def test_filter(pyspark_constructor: Constructor) -> None:
     assert_equal_data(result, expected)
 
 
-@pytest.mark.filterwarnings("ignore:If `index_col` is not specified for `to_spark`")
-def test_filter_with_boolean_list(pyspark_constructor: Constructor) -> None:
-    data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]}
-    df = nw.from_native(pyspark_constructor(data))
-
-    with pytest.raises(
-        NotImplementedError,
-        match="`LazyFrame.filter` is not supported for PySpark backend with boolean masks.",
-    ):
-        _ = df.filter([False, True, True])
-
-
 # copied from tests/frame/schema_test.py
 @pytest.mark.filterwarnings("ignore:Determining|Resolving.*")
 def test_schema(pyspark_constructor: Constructor) -> None: